Skip to content

Stats Review: Add Utilities (P6) - #24718

Closed
kean wants to merge 1 commit into
review/mock-servicefrom
review/stats-utilities
Closed

Stats Review: Add Utilities (P6)#24718
kean wants to merge 1 commit into
review/mock-servicefrom
review/stats-utilities

Conversation

@kean

@kean kean commented Aug 5, 2025

Copy link
Copy Markdown
Contributor

The PR adds small utilities and views like CSVExporter, CardModifier, ScrollOffsetModifier for compatibility with previous versions and more.

One notable change is TrendViewModel that shows trends/change over time. See the unit tests to learn what it does and how.

@kean kean added this to the Pending milestone Aug 5, 2025
@kean kean added the Stats label Aug 5, 2025
@kean kean mentioned this pull request Aug 5, 2025
14 tasks
@dangermattic

Copy link
Copy Markdown
Collaborator
1 Warning
⚠️ This PR is larger than 500 lines of changes. Please consider splitting it into smaller PRs for easier and faster reviews.

Generated by 🚫 Danger

@kean kean changed the title Add Utilities (P6) Stats Review: Add Utilities (P6) Aug 5, 2025
@sonarqubecloud

sonarqubecloud Bot commented Aug 5, 2025

Copy link
Copy Markdown

// Characters that require field escaping according to RFC 4180
static let charactersRequiringEscape = CharacterSet(charactersIn: ",\"\r\n")

func generateCSV(from items: [any TopListItemProtocol], metric: SiteMetric) -> String {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does the item need to be TopListItemProtocol? Can they just be CSVExportable?

If they do need to be both, you can declare the function as below to avoid casting:

func generateCSV<T: TopListItemProtocol + CSVExportable>(from items: [T], metric: SiteMetric) -> String

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.

The app uses [any TopListItemProtocol] throughout (the alternative would've required to make a ton of types generic making them more complicated than needed), so we don't know the exact type at compile time when we invoke generate.

@@ -0,0 +1,19 @@
import SwiftUI

struct CardModifier: ViewModifier {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nitpick: This modifier does not seem to be necessary. I think we can move the function body to func cardStyle()?

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.

It's not strictly necessary, but it's an idiomatic approach if you have multiple adjustments to make. Typically, you would use a modifier when you have something more complex: need to access environment values, etc. It does produce a shorter type signature, which is easier to see during debugging and (might) have performance implications, so I think modifiers are generally the way to go for scenarios like this.


extension View {
@ViewBuilder
func trackScrollOffset(isScrolling: Binding<Bool>) -> some View {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If I'm not mistaken, the ScrollOffsetModifier checks whether the scroll view has been scrolled, not whether it's currently scrolling? If that's the case, the name isScrolling might be a bit misleading?

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.

Yeah, I forgot to update it. Done.

import Foundation

struct SelectedDataPoints {
let current: DataPoint?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should this property be optional? None of the static functions below creates an instance with current = nil.

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.

They do as on a chart you can select an area (date) either the current point, or the previous point, or both, or with no points (SelectedDataPoints?).

/// A completed formatted trend with the absolute change and the percentage change.
var formattedTrendShort2: String {
"\(formattedChange)   \(iconSign) \(formattedPercentage)"
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The two "short" strings are both longer than formattedTrend.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also, maybe we don't want to have two variables named foo and foo2?

@kean kean Aug 25, 2025

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.

I made the following changes:

  • Inline formattedTrendShort in WeeklyTrendsView as it's the only place where it's used
  • Rename formattedTrend to formattedTrendShort
  • Rename formattedTrendShort2 to formattedTrendShort

Not great that it uses two spaces there, but I'd keep this for now.

Commit: 3738445

@kean kean closed this Aug 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants