Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Fakes/Fakes/Networking.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1593,6 +1593,19 @@ extension SiteSettingGroup {
.general
}
}
extension Networking.SiteSummaryStats {
/// Returns a "ready to use" type filled with fake values.
///
public static func fake() -> Networking.SiteSummaryStats {
.init(
siteID: .fake(),
date: .fake(),
period: .fake(),
visitors: .fake(),
views: .fake()
)
}
}
extension Networking.SiteVisitStats {
/// Returns a "ready to use" type filled with fake values.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1912,6 +1912,30 @@ extension Networking.SiteSetting {
}
}

extension Networking.SiteSummaryStats {
public func copy(
siteID: CopiableProp<Int64> = .copy,
date: CopiableProp<String> = .copy,
period: CopiableProp<StatGranularity> = .copy,
visitors: CopiableProp<Int> = .copy,
views: CopiableProp<Int> = .copy
) -> Networking.SiteSummaryStats {
let siteID = siteID ?? self.siteID
let date = date ?? self.date
let period = period ?? self.period
let visitors = visitors ?? self.visitors
let views = views ?? self.views

return Networking.SiteSummaryStats(
siteID: siteID,
date: date,
period: period,
visitors: visitors,
views: views
)
}
}

extension Networking.SiteVisitStats {
public func copy(
siteID: CopiableProp<Int64> = .copy,
Expand Down
2 changes: 1 addition & 1 deletion Networking/Networking/Model/Stats/SiteSummaryStats.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Codegen

/// Represents site summary stats for a specific period.
///
public struct SiteSummaryStats: Decodable {
public struct SiteSummaryStats: Decodable, GeneratedCopiable, GeneratedFakeable {
public let siteID: Int64
public let date: String
public let period: StatGranularity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,27 +118,37 @@ struct StatsDataTextFormatter {
return createDeltaPercentage(from: previousCount, to: currentCount)
}

/// Creates the text to display for the views count.
///
static func createViewsCountText(siteStats: SiteSummaryStats?) -> String {
guard let viewsCount = siteStats?.views else {
return Constants.placeholderText
}

return Double(viewsCount).humanReadableString()
}

// MARK: Conversion Stats

/// Creates the text to display for the conversion rate.
///
static func createConversionRateText(orderStats: OrderStatsV4?, siteStats: SiteVisitStats?, selectedIntervalIndex: Int?) -> String {
let visitors = visitorCount(at: selectedIntervalIndex, siteStats: siteStats)
let orders = orderCount(at: selectedIntervalIndex, orderStats: orderStats)
guard let visitors = visitorCount(at: selectedIntervalIndex, siteStats: siteStats),
let orders = orderCount(at: selectedIntervalIndex, orderStats: orderStats) else {
return Constants.placeholderText
}

let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .percent
numberFormatter.minimumFractionDigits = 1
return createConversionRateText(converted: orders, total: visitors)
}

if let visitors, let orders {
// Maximum conversion rate is 100%.
let conversionRate = visitors > 0 ? min(orders/visitors, 1): 0
let minimumFractionDigits = floor(conversionRate * 100.0) == conversionRate * 100.0 ? 0: 1
numberFormatter.minimumFractionDigits = minimumFractionDigits
return numberFormatter.string(from: conversionRate as NSNumber) ?? Constants.placeholderText
} else {
/// Creates the text to display for the conversion rate based on SiteSummaryStats data.
///
static func createConversionRateText(orderStats: OrderStatsV4?, siteStats: SiteSummaryStats?) -> String {
guard let visitors = siteStats?.visitors, let orders = orderStats?.totals.totalOrders else {
return Constants.placeholderText
}

return createConversionRateText(converted: Double(orders), total: Double(visitors))
}

// MARK: Product Stats
Expand Down Expand Up @@ -261,9 +271,21 @@ private extension StatsDataTextFormatter {
}
}

/// Creates the text to display for the conversion rate from 2 input values.
///
static func createConversionRateText(converted: Double, total: Double) -> String {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .percent
numberFormatter.minimumFractionDigits = 1

// Maximum conversion rate is 100%.
let conversionRate = total > 0 ? min(converted/total, 1) : 0
let minimumFractionDigits = floor(conversionRate * 100.0) == conversionRate * 100.0 ? 0 : 1
numberFormatter.minimumFractionDigits = minimumFractionDigits
return numberFormatter.string(from: conversionRate as NSNumber) ?? Constants.placeholderText
}

enum Constants {
static let placeholderText = "-"
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -333,9 +333,20 @@ final class StatsDataTextFormatterTests: XCTestCase {
XCTAssertEqual(visitorCountDelta.direction, .positive)
}

func test_createViewsCountText_returns_expected_views_stats() {
// Given
let siteVisitStats = SiteSummaryStats.fake().copy(views: 250)

// When
let viewsCount = StatsDataTextFormatter.createViewsCountText(siteStats: siteVisitStats)

// Then
XCTAssertEqual(viewsCount, "250")
}

// MARK: Conversion Stats

func test_createConversionRateText_returns_placeholder_when_visitor_count_is_zero() {
func test_createConversionRateText_for_SiteVisitStats_returns_placeholder_when_visitor_count_is_zero() {
// Given
let siteVisitStats = Yosemite.SiteVisitStats.fake().copy(items: [.fake().copy(visitors: 0)])
let orderStats = OrderStatsV4.fake().copy(totals: .fake().copy(totalOrders: 3))
Expand All @@ -347,7 +358,7 @@ final class StatsDataTextFormatterTests: XCTestCase {
XCTAssertEqual(conversionRate, "0%")
}

func test_createConversionRateText_returns_one_decimal_point_when_percentage_value_has_two_decimal_points() {
func test_createConversionRateText_for_SiteVisitStats_returns_one_decimal_point_when_percentage_value_has_two_decimal_points() {
// Given
let siteVisitStats = Yosemite.SiteVisitStats.fake().copy(items: [.fake().copy(visitors: 10000)])
let orderStats = OrderStatsV4.fake().copy(totals: .fake().copy(totalOrders: 3557))
Expand All @@ -359,7 +370,7 @@ final class StatsDataTextFormatterTests: XCTestCase {
XCTAssertEqual(conversionRate, "35.6%") // order count: 3557, visitor count: 10000 => 0.3557 (35.57%)
}

func test_createConversionRateText_returns_no_decimal_point_when_percentage_value_is_integer() {
func test_createConversionRateText_for_SiteVisitStats_returns_no_decimal_point_when_percentage_value_is_integer() {
// Given
let siteVisitStats = Yosemite.SiteVisitStats.fake().copy(items: [.fake().copy(visitors: 10)])
let orderStats = OrderStatsV4.fake().copy(totals: .fake().copy(totalOrders: 3))
Expand All @@ -371,7 +382,7 @@ final class StatsDataTextFormatterTests: XCTestCase {
XCTAssertEqual(conversionRate, "30%") // order count: 3, visitor count: 10 => 0.3 (30%)
}

func test_createConversionRateText_returns_expected_text_for_selected_interval() {
func test_createConversionRateText_for_SiteVisitStats_returns_expected_text_for_selected_interval() {
// Given
let siteVisitStats = Yosemite.SiteVisitStats.fake().copy(items: [.fake().copy(visitors: 10)])
let orderStats = OrderStatsV4.fake().copy(totals: .fake().copy(totalOrders: 2),
Expand All @@ -384,6 +395,42 @@ final class StatsDataTextFormatterTests: XCTestCase {
XCTAssertEqual(conversionRate, "10%")
}

func test_createConversionRateText_for_SiteSummaryStats_returns_placeholder_when_visitor_count_is_zero() {
// Given
let siteSummaryStats = SiteSummaryStats.fake().copy(visitors: 0)
let orderStats = OrderStatsV4.fake().copy(totals: .fake().copy(totalOrders: 3))

// When
let conversionRate = StatsDataTextFormatter.createConversionRateText(orderStats: orderStats, siteStats: siteSummaryStats)

// Then
XCTAssertEqual(conversionRate, "0%")
}

func test_createConversionRateText_for_SiteSummaryStats_returns_one_decimal_point_when_percentage_value_has_two_decimal_points() {
// Given
let siteSummaryStats = SiteSummaryStats.fake().copy(visitors: 10000)
let orderStats = OrderStatsV4.fake().copy(totals: .fake().copy(totalOrders: 3557))

// When
let conversionRate = StatsDataTextFormatter.createConversionRateText(orderStats: orderStats, siteStats: siteSummaryStats)

// Then
XCTAssertEqual(conversionRate, "35.6%") // order count: 3557, visitor count: 10000 => 0.3557 (35.57%)
}

func test_createConversionRateText_for_SiteSummaryStats_returns_no_decimal_point_when_percentage_value_is_integer() {
// Given
let siteSummaryStats = SiteSummaryStats.fake().copy(visitors: 10)
let orderStats = OrderStatsV4.fake().copy(totals: .fake().copy(totalOrders: 3))

// When
let conversionRate = StatsDataTextFormatter.createConversionRateText(orderStats: orderStats, siteStats: siteSummaryStats)

// Then
XCTAssertEqual(conversionRate, "30%") // order count: 3, visitor count: 10 => 0.3 (30%)
}

// MARK: Delta Calculations

func test_createDeltaPercentage_returns_expected_positive_delta() {
Expand Down
1 change: 1 addition & 0 deletions Yosemite/Yosemite/Model/Model.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ public typealias SitePlugin = Networking.SitePlugin
public typealias SitePluginStatusEnum = Networking.SitePluginStatusEnum
public typealias SiteSetting = Networking.SiteSetting
public typealias SiteSettingGroup = Networking.SiteSettingGroup
public typealias SiteSummaryStats = Networking.SiteSummaryStats
public typealias SiteVisitStats = Networking.SiteVisitStats
public typealias SiteVisitStatsItem = Networking.SiteVisitStatsItem
public typealias StateOfACountry = Networking.StateOfACountry
Expand Down