-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add WordAdsMetric and related APIs #25152
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
Open
kean
wants to merge
1
commit into
trunk
Choose a base branch
from
task/add-chart-view
base: trunk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+843
−209
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 20 additions & 0 deletions
20
Modules/Sources/JetpackStats/Services/Data/MetricType.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import SwiftUI | ||
|
|
||
| /// Protocol defining the requirements for a metric type that can be displayed in stats views. | ||
| protocol MetricType: Identifiable, Hashable, Equatable { | ||
| var localizedTitle: String { get } | ||
| var systemImage: String { get } | ||
| var primaryColor: Color { get } | ||
| var isHigherValueBetter: Bool { get } | ||
| var aggregationStrategy: AggregationStrategy { get } | ||
|
|
||
| /// Creates the appropriate value formatter for this metric type. | ||
| func makeValueFormatter() -> any ValueFormatterProtocol | ||
| } | ||
|
|
||
| enum AggregationStrategy: Sendable { | ||
| /// Simply sum the values for the given period. | ||
| case sum | ||
| /// Calculate the average value for the given period. | ||
| case average | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
Modules/Sources/JetpackStats/Services/Data/WordAdsMetric.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import SwiftUI | ||
|
|
||
| struct WordAdsMetric: Identifiable, Sendable, Hashable, MetricType { | ||
| let id: String | ||
| let localizedTitle: String | ||
| let systemImage: String | ||
| let primaryColor: Color | ||
| let aggregationStrategy: AggregationStrategy | ||
| let isHigherValueBetter: Bool | ||
|
|
||
| private init( | ||
| id: String, | ||
| localizedTitle: String, | ||
| systemImage: String, | ||
| primaryColor: Color, | ||
| aggregationStrategy: AggregationStrategy, | ||
| isHigherValueBetter: Bool = true | ||
| ) { | ||
| self.id = id | ||
| self.localizedTitle = localizedTitle | ||
| self.systemImage = systemImage | ||
| self.primaryColor = primaryColor | ||
| self.aggregationStrategy = aggregationStrategy | ||
| self.isHigherValueBetter = isHigherValueBetter | ||
| } | ||
|
|
||
| func backgroundColor(in colorScheme: ColorScheme) -> Color { | ||
| primaryColor.opacity(colorScheme == .light ? 0.05 : 0.15) | ||
| } | ||
|
|
||
| static func == (lhs: WordAdsMetric, rhs: WordAdsMetric) -> Bool { | ||
| lhs.id == rhs.id | ||
| } | ||
|
|
||
| func hash(into hasher: inout Hasher) { | ||
| hasher.combine(id) | ||
| } | ||
|
|
||
| func makeValueFormatter() -> any ValueFormatterProtocol { | ||
| WordAdsValueFormatter(metric: self) | ||
| } | ||
|
|
||
| // MARK: - Static Metrics | ||
|
|
||
| static let impressions = WordAdsMetric( | ||
| id: "impressions", | ||
| localizedTitle: Strings.WordAdsMetrics.adsServed, | ||
| systemImage: "eye", | ||
| primaryColor: Constants.Colors.blue, | ||
| aggregationStrategy: .sum | ||
| ) | ||
|
|
||
| static let cpm = WordAdsMetric( | ||
| id: "cpm", | ||
| localizedTitle: Strings.WordAdsMetrics.averageCPM, | ||
| systemImage: "chart.bar", | ||
| primaryColor: Constants.Colors.green, | ||
| aggregationStrategy: .average | ||
| ) | ||
|
|
||
| static let revenue = WordAdsMetric( | ||
| id: "revenue", | ||
| localizedTitle: Strings.WordAdsMetrics.revenue, | ||
| systemImage: "dollarsign.circle", | ||
| primaryColor: Constants.Colors.green, | ||
| aggregationStrategy: .sum | ||
| ) | ||
|
|
||
| static let allMetrics: [WordAdsMetric] = [.impressions, .cpm, .revenue] | ||
| } |
13 changes: 13 additions & 0 deletions
13
Modules/Sources/JetpackStats/Services/Data/WordAdsMetricsResponse.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import Foundation | ||
|
|
||
| struct WordAdsMetricsResponse: Sendable { | ||
| var total: WordAdsMetricsSet | ||
|
|
||
| /// Data points with the requested granularity. | ||
| /// | ||
| /// - note: The dates are in the site reporting time zone. | ||
| /// | ||
| /// - warning: Hourly data is not available for some metrics, but total | ||
| /// metrics still are. | ||
| var metrics: [WordAdsMetric: [DataPoint]] | ||
| } |
35 changes: 35 additions & 0 deletions
35
Modules/Sources/JetpackStats/Services/Data/WordAdsMetricsSet.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import Foundation | ||
|
|
||
| /// A memory-efficient collection of WordAds metrics with direct memory layout. | ||
| struct WordAdsMetricsSet: Codable, Sendable { | ||
| var impressions: Int? | ||
| var cpm: Int? // Stored in cents | ||
| var revenue: Int? // Stored in cents | ||
|
|
||
| subscript(metric: WordAdsMetric) -> Int? { | ||
| get { | ||
| switch metric.id { | ||
| case "impressions": impressions | ||
| case "cpm": cpm | ||
| case "revenue": revenue | ||
| default: nil | ||
| } | ||
| } | ||
| set { | ||
| switch metric.id { | ||
| case "impressions": impressions = newValue | ||
| case "cpm": cpm = newValue | ||
| case "revenue": revenue = newValue | ||
| default: break | ||
| } | ||
| } | ||
| } | ||
|
|
||
| static var mock: WordAdsMetricsSet { | ||
| WordAdsMetricsSet( | ||
| impressions: Int.random(in: 1000...10000), | ||
| cpm: Int.random(in: 100...500), // $1.00 - $5.00 in cents | ||
| revenue: Int.random(in: 1000...10000) // $10.00 - $100.00 in cents | ||
| ) | ||
| } | ||
| } |
143 changes: 143 additions & 0 deletions
143
Modules/Sources/JetpackStats/Services/Extensions/WordPressKit+Extensions.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| @preconcurrency import WordPressKit | ||
|
|
||
| extension WordPressKit.StatsServiceRemoteV2 { | ||
| /// A modern variant of `WordPressKit.StatsTimeIntervalData` API that supports | ||
| /// custom date periods. | ||
| func getData<TimeStatsType: WordPressKit.StatsTimeIntervalData>( | ||
| interval: DateInterval, | ||
| unit: WordPressKit.StatsPeriodUnit, | ||
| summarize: Bool? = nil, | ||
| limit: Int, | ||
| parameters: [String: String]? = nil | ||
| ) async throws -> TimeStatsType where TimeStatsType: Sendable { | ||
| try await withCheckedThrowingContinuation { continuation in | ||
| // `period` is ignored if you pass `startDate`, but it's a required parameter | ||
| getData(for: unit, unit: unit, startDate: interval.start, endingOn: interval.end, limit: limit, summarize: summarize, parameters: parameters) { (data: TimeStatsType?, error: Error?) in | ||
| if let data { | ||
| continuation.resume(returning: data) | ||
| } else { | ||
| continuation.resume(throwing: error ?? StatsServiceError.unknown) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// A legacy variant of `WordPressKit.StatsTimeIntervalData` API that supports | ||
| /// only support setting the target date and the quantity of periods to return. | ||
| func getData<TimeStatsType: WordPressKit.StatsTimeIntervalData>( | ||
| date: Date, | ||
| unit: WordPressKit.StatsPeriodUnit, | ||
| quantity: Int | ||
| ) async throws -> TimeStatsType where TimeStatsType: Sendable { | ||
| try await withCheckedThrowingContinuation { continuation in | ||
| // Call getData with date and quantity (quantity is passed as limit, which becomes maxCount in queryProperties) | ||
| getData(for: unit, endingOn: date, limit: quantity) { (data: TimeStatsType?, error: Error?) in | ||
| if let data { | ||
| continuation.resume(returning: data) | ||
| } else { | ||
| continuation.resume(throwing: error ?? StatsServiceError.unknown) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func getInsight<InsightType: StatsInsightData>(limit: Int = 10) async throws -> InsightType where InsightType: Sendable { | ||
| try await withCheckedThrowingContinuation { continuation in | ||
| getInsight(limit: limit) { (insight: InsightType?, error: Error?) in | ||
| if let insight { | ||
| continuation.resume(returning: insight) | ||
| } else { | ||
| continuation.resume(throwing: error ?? StatsServiceError.unknown) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func getDetails(forPostID postID: Int) async throws -> StatsPostDetails { | ||
| try await withCheckedThrowingContinuation { continuation in | ||
| getDetails(forPostID: postID) { (details: StatsPostDetails?, error: Error?) in | ||
| if let details { | ||
| continuation.resume(returning: details) | ||
| } else { | ||
| continuation.resume(throwing: error ?? StatsServiceError.unknown) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func getInsight(limit: Int = 10) async throws -> StatsLastPostInsight { | ||
| try await withCheckedThrowingContinuation { continuation in | ||
| getInsight(limit: limit) { (insight: StatsLastPostInsight?, error: Error?) in | ||
| if let insight { | ||
| continuation.resume(returning: insight) | ||
| } else { | ||
| continuation.resume(throwing: error ?? StatsServiceError.unknown) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func toggleSpamState(for referrerDomain: String, currentValue: Bool) async throws { | ||
| try await withCheckedThrowingContinuation { continuation in | ||
| toggleSpamState(for: referrerDomain, currentValue: currentValue, success: { | ||
| continuation.resume() | ||
| }, failure: { error in | ||
| continuation.resume(throwing: error) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func getEmailSummaryData( | ||
| quantity: Int, | ||
| sortField: StatsEmailsSummaryData.SortField = .opens, | ||
| sortOrder: StatsEmailsSummaryData.SortOrder = .descending | ||
| ) async throws -> StatsEmailsSummaryData { | ||
| try await withCheckedThrowingContinuation { continuation in | ||
| getData(quantity: quantity, sortField: sortField, sortOrder: sortOrder) { result in | ||
| switch result { | ||
| case .success(let data): | ||
| continuation.resume(returning: data) | ||
| case .failure(let error): | ||
| continuation.resume(throwing: error) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func getEmailOpens(for postID: Int) async throws -> StatsEmailOpensData { | ||
| try await withCheckedThrowingContinuation { continuation in | ||
| getEmailOpens(for: postID) { (data, error) in | ||
| if let data { | ||
| continuation.resume(returning: data) | ||
| } else { | ||
| continuation.resume(throwing: error ?? StatsServiceError.unknown) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| extension WordPressKit.StatsPeriodUnit { | ||
| init(_ granularity: DateRangeGranularity) { | ||
| switch granularity { | ||
| case .hour: self = .hour | ||
| case .day: self = .day | ||
| case .week: self = .week | ||
| case .month: self = .month | ||
| case .year: self = .year | ||
| } | ||
| } | ||
| } | ||
|
|
||
| extension WordPressKit.StatsSiteMetricsResponse.Metric { | ||
| init?(_ metric: SiteMetric) { | ||
| switch metric { | ||
| case .views: self = .views | ||
| case .visitors: self = .visitors | ||
| case .likes: self = .likes | ||
| case .comments: self = .comments | ||
| case .posts: self = .posts | ||
| case .timeOnSite, .bounceRate, .downloads: return nil | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of this code was simply moved from the bottom of
StatsService.swift. The only addition is the method: