-
Notifications
You must be signed in to change notification settings - Fork 109
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #233 from woocommerce/develop
Merging Mark 0.5 into Master
- Loading branch information
Showing
111 changed files
with
18,673 additions
and
1,916 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains 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
This file contains 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,14 @@ | ||
import Foundation | ||
|
||
|
||
/// Mapper: OrderStats | ||
/// | ||
class OrderStatsMapper: Mapper { | ||
|
||
/// (Attempts) to convert a dictionary into an OrderStats entity. | ||
/// | ||
func map(response: Data) throws -> OrderStats { | ||
let decoder = JSONDecoder() | ||
return try decoder.decode(OrderStats.self, from: response) | ||
} | ||
} |
This file contains 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,14 @@ | ||
import Foundation | ||
|
||
|
||
/// Mapper: SiteVisitStats | ||
/// | ||
class SiteVisitStatsMapper: Mapper { | ||
|
||
/// (Attempts) to convert a dictionary into an SiteVisitStats entity. | ||
/// | ||
func map(response: Data) throws -> SiteVisitStats { | ||
let decoder = JSONDecoder() | ||
return try decoder.decode(SiteVisitStats.self, from: response) | ||
} | ||
} |
This file contains 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
This file contains 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,73 @@ | ||
import Foundation | ||
|
||
/** | ||
This is a generic container data container used to hold an (unkeyed) data array | ||
of which its elements can be multiple types. Additionally, the field names | ||
are stored in a separate array where the specific index of a field name element | ||
corresponds to its matching element in the `data` array. | ||
Why do we have this insanity? To deal with JSON payloads that can look like this: | ||
```` | ||
{ | ||
"fields": [ | ||
"period", | ||
"orders", | ||
"total_sales", | ||
"total_tax", | ||
"total_shipping", | ||
"currency", | ||
"gross_sales" | ||
], | ||
"data": [ | ||
[ "2018-06-01", 2, 14.24, 9.98, 0.28, "USD", 14.120000000000001 ], | ||
[ 2018, 2, 123123, 9.98, 0.0, "USD", 0] | ||
] | ||
... | ||
} | ||
```` | ||
A few accessor methods are also provided that will ensure the correct type is returned for a given field. This container | ||
will be especially useful when dealing with data returned from the stats endpoints. 😃 | ||
*/ | ||
public struct MIContainer { | ||
let data: [Any] | ||
let fieldNames: [String] | ||
|
||
func fetchStringValue<T : RawRepresentable>(for field: T) -> String where T.RawValue == String { | ||
guard let index = fieldNames.index(of: field.rawValue) else { | ||
return "" | ||
} | ||
|
||
// 😢 As crazy as it sounds, sometimes the server occasionally returns | ||
// String values as Ints — we need to account for this. | ||
if self.data[index] is Int { | ||
if let intValue = self.data[index] as? Int { | ||
return String(intValue) | ||
} | ||
return "" | ||
} else { | ||
return self.data[index] as? String ?? "" | ||
} | ||
} | ||
|
||
func fetchIntValue<T : RawRepresentable>(for field: T) -> Int where T.RawValue == String { | ||
guard let index = fieldNames.index(of: field.rawValue), | ||
let returnValue = self.data[index] as? Int else { | ||
return 0 | ||
} | ||
return returnValue | ||
} | ||
|
||
func fetchDoubleValue<T : RawRepresentable>(for field: T) -> Double where T.RawValue == String { | ||
guard let index = fieldNames.index(of: field.rawValue) else { | ||
return 0 | ||
} | ||
|
||
if self.data[index] is Int { | ||
let intValue = self.data[index] as? Int ?? 0 | ||
return Double(intValue) | ||
} else { | ||
return self.data[index] as? Double ?? 0 | ||
} | ||
} | ||
} |
This file contains 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,115 @@ | ||
import Foundation | ||
|
||
|
||
/// Represents order stats over a specific period. | ||
/// | ||
public struct OrderStats: Decodable { | ||
public let date: String | ||
public let granularity: StatGranularity | ||
public let quantity: String | ||
public let fields: [String] | ||
public let totalGrossSales: Float | ||
public let totalNetSales: Float | ||
public let totalOrders: Int | ||
public let totalProducts: Int | ||
public let averageGrossSales: Float | ||
public let averageNetSales: Float | ||
public let averageOrders: Float | ||
public let averageProducts: Float | ||
public let items: [OrderStatsItem]? | ||
|
||
|
||
/// The public initializer for order stats. | ||
/// | ||
public init(from decoder: Decoder) throws { | ||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
|
||
let date = try container.decode(String.self, forKey: .date) | ||
let granularity = try container.decode(StatGranularity.self, forKey: .unit) | ||
let quantity = try container.decode(String.self, forKey: .quantity) | ||
|
||
let fields = try container.decode([String].self, forKey: .fields) | ||
let rawData: [[AnyCodable]] = try container.decode([[AnyCodable]].self, forKey: .data) | ||
|
||
let totalGrossSales = try container.decode(Float.self, forKey: .totalGrossSales) | ||
let totalNetSales = try container.decode(Float.self, forKey: .totalNetSales) | ||
let totalOrders = try container.decode(Int.self, forKey: .totalOrders) | ||
let totalProducts = try container.decode(Int.self, forKey: .totalProducts) | ||
|
||
let averageGrossSales = try container.decode(Float.self, forKey: .averageGrossSales) | ||
let averageNetSales = try container.decode(Float.self, forKey: .averageNetSales) | ||
let averageOrders = try container.decode(Float.self, forKey: .averageOrders) | ||
let averageProducts = try container.decode(Float.self, forKey: .averageProducts) | ||
|
||
let items = rawData.map({ OrderStatsItem(fieldNames: fields, rawData: $0) }) | ||
|
||
self.init(date: date, granularity: granularity, quantity: quantity, fields: fields, items: items, totalGrossSales: totalGrossSales, totalNetSales: totalNetSales, totalOrders: totalOrders, totalProducts: totalProducts, averageGrossSales: averageGrossSales, averageNetSales: averageNetSales, averageOrders: averageOrders, averageProducts: averageProducts) | ||
} | ||
|
||
|
||
/// OrderStats struct initializer. | ||
/// | ||
public init(date: String, granularity: StatGranularity, quantity: String, fields: [String], items: [OrderStatsItem]?, totalGrossSales: Float, totalNetSales: Float, totalOrders: Int, totalProducts: Int, averageGrossSales: Float, averageNetSales: Float, averageOrders: Float, averageProducts: Float) { | ||
self.date = date | ||
self.granularity = granularity | ||
self.quantity = quantity | ||
self.fields = fields | ||
self.totalGrossSales = totalGrossSales | ||
self.totalNetSales = totalNetSales | ||
self.totalOrders = totalOrders | ||
self.totalProducts = totalProducts | ||
self.averageGrossSales = averageGrossSales | ||
self.averageNetSales = averageNetSales | ||
self.averageOrders = averageOrders | ||
self.averageProducts = averageProducts | ||
self.items = items | ||
} | ||
} | ||
|
||
|
||
/// Defines all of the OrderStats CodingKeys. | ||
/// | ||
private extension OrderStats { | ||
|
||
enum CodingKeys: String, CodingKey { | ||
case date = "date" | ||
case unit = "unit" | ||
case quantity = "quantity" | ||
case fields = "fields" | ||
case data = "data" | ||
case totalGrossSales = "total_gross_sales" | ||
case totalNetSales = "total_net_sales" | ||
case totalOrders = "total_orders" | ||
case totalProducts = "total_products" | ||
case averageGrossSales = "avg_gross_sales" | ||
case averageNetSales = "avg_net_sales" | ||
case averageOrders = "avg_orders" | ||
case averageProducts = "avg_products" | ||
} | ||
} | ||
|
||
|
||
// MARK: - Comparable Conformance | ||
// | ||
extension OrderStats: Comparable { | ||
public static func == (lhs: OrderStats, rhs: OrderStats) -> Bool { | ||
return lhs.date == rhs.date && | ||
lhs.granularity == rhs.granularity && | ||
lhs.quantity == rhs.quantity && | ||
lhs.fields == rhs.fields && | ||
lhs.totalGrossSales == rhs.totalGrossSales && | ||
lhs.totalNetSales == rhs.totalNetSales && | ||
lhs.totalOrders == rhs.totalOrders && | ||
lhs.totalProducts == rhs.totalProducts && | ||
lhs.averageGrossSales == rhs.averageGrossSales && | ||
lhs.averageNetSales == rhs.averageNetSales && | ||
lhs.averageOrders == rhs.averageOrders && | ||
lhs.averageProducts == rhs.averageProducts && | ||
lhs.items == rhs.items | ||
} | ||
|
||
public static func < (lhs: OrderStats, rhs: OrderStats) -> Bool { | ||
return lhs.date < rhs.date || | ||
(lhs.date == rhs.date && lhs.quantity < rhs.quantity) | ||
} | ||
} |
Oops, something went wrong.