Skip to content
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

Stats: Add Subscriber Chart #23074

Merged
merged 9 commits into from
Apr 26, 2024
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
2 changes: 1 addition & 1 deletion Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ end

def wordpress_kit
# pod 'WordPressKit', '~> 17.0.0'
pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', commit: '14aa53a2e1cfa764e3e9e3e91d1f39f4ef09e098'
pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', commit: '77aee91d607cb8b86d4356c0aebfb3977ff1fcc7'
# pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', branch: ''
# pod 'WordPressKit', git: 'https://github.com/wordpress-mobile/WordPressKit-iOS.git', tag: ''
# pod 'WordPressKit', path: '../WordPressKit-iOS'
Expand Down
8 changes: 4 additions & 4 deletions Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ DEPENDENCIES:
- SwiftLint (= 0.54.0)
- WordPress-Editor-iOS (~> 1.19.11)
- WordPressAuthenticator (>= 9.0.8, ~> 9.0)
- WordPressKit (from `https://github.com/wordpress-mobile/WordPressKit-iOS.git`, commit `14aa53a2e1cfa764e3e9e3e91d1f39f4ef09e098`)
- WordPressKit (from `https://github.com/wordpress-mobile/WordPressKit-iOS.git`, commit `77aee91d607cb8b86d4356c0aebfb3977ff1fcc7`)
- WordPressShared (from `https://github.com/wordpress-mobile/WordPress-iOS-Shared.git`, commit `688ee5e4efddc1fc23626626ef17b7e929bdafb0`)
- WordPressUI (~> 1.16)
- ZendeskSupportSDK (= 5.3.0)
Expand Down Expand Up @@ -177,7 +177,7 @@ EXTERNAL SOURCES:
Gutenberg:
:podspec: https://cdn.a8c-ci.services/gutenberg-mobile/Gutenberg-v1.117.0.podspec
WordPressKit:
:commit: 14aa53a2e1cfa764e3e9e3e91d1f39f4ef09e098
:commit: 77aee91d607cb8b86d4356c0aebfb3977ff1fcc7
:git: https://github.com/wordpress-mobile/WordPressKit-iOS.git
WordPressShared:
:commit: 688ee5e4efddc1fc23626626ef17b7e929bdafb0
Expand All @@ -188,7 +188,7 @@ CHECKOUT OPTIONS:
:git: https://github.com/wordpress-mobile/FSInteractiveMap.git
:tag: 0.2.0
WordPressKit:
:commit: 14aa53a2e1cfa764e3e9e3e91d1f39f4ef09e098
:commit: 77aee91d607cb8b86d4356c0aebfb3977ff1fcc7
:git: https://github.com/wordpress-mobile/WordPressKit-iOS.git
WordPressShared:
:commit: 688ee5e4efddc1fc23626626ef17b7e929bdafb0
Expand Down Expand Up @@ -239,6 +239,6 @@ SPEC CHECKSUMS:
ZendeskSupportSDK: 3a8e508ab1d9dd22dc038df6c694466414e037ba
ZIPFoundation: d170fa8e270b2a32bef9dcdcabff5b8f1a5deced

PODFILE CHECKSUM: 4ac1d35f8415bdc8d4c8e39d6aa7a9f6dab5933d
PODFILE CHECKSUM: 4bbf2ae7c80a5f39db237e7c3514872e9f7eb3ca

COCOAPODS: 1.15.2
4 changes: 4 additions & 0 deletions WordPress/Classes/ViewRelated/Stats/Helpers/StatSection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
case postStatsMonthsYears
case postStatsAverageViews
case postStatsRecentWeeks
case subscribersChart
case subscribersEmailsSummary
case subscribersList

Expand Down Expand Up @@ -144,6 +145,8 @@
return PostStatsHeaders.averageViewsPerDay
case .postStatsRecentWeeks:
return PostStatsHeaders.recentWeeks
case .subscribersChart:
return SubscribersHeaders.chart
case .subscribersEmailsSummary:
return SubscribersHeaders.emailsSummaryStats
case .subscribersList:
Expand Down Expand Up @@ -433,6 +436,7 @@
}

struct SubscribersHeaders {
static let chart = NSLocalizedString("stats.subscribers.chart.title", value: "Subscribers", comment: "Stats 'Subscribers' card header, contains chart")
static let emailsSummaryStats = NSLocalizedString("stats.subscribers.emailsSummaryCard.title", value: "Emails", comment: "Stats 'Emails' card header")
static let subscribersList = NSLocalizedString("stats.subscribers.subscribersListCard.title", value: "Subscribers", comment: "Stats 'Subscribers' card header")
}
Expand Down
29 changes: 29 additions & 0 deletions WordPress/Classes/ViewRelated/Stats/SiteStatsTableViewCells.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,35 @@ struct ViewsVisitorsRow: StatsHashableImmuTableRow {
}
}

struct SubscriberChartRow: StatsHashableImmuTableRow {
typealias CellType = StatsSubscribersChartCell

static let cell: ImmuTableCell = {
return ImmuTableCell.nib(CellType.defaultNib, CellType.self)
}()

let action: ImmuTableAction? = nil
let history: [StatsSubscribersSummaryData.SubscriberData]
let chartData: LineChartDataConvertible
let chartStyling: LineChartStyling
let xAxisDates: [Date]
let statSection: StatSection?

static func == (lhs: SubscriberChartRow, rhs: SubscriberChartRow) -> Bool {
return lhs.xAxisDates == rhs.xAxisDates &&
lhs.history == rhs.history
}

func configureCell(_ cell: UITableViewCell) {

guard let cell = cell as? CellType else {
return
}

cell.configure(row: self)
}
}

struct CellHeaderRow: StatsHashableImmuTableRow {

typealias CellType = StatsCellHeader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ final class StatsSubscribersCache {
return .init(record: .subscribersEmailsSummary, key: "\(quantity) \(sortField) \(sortOrder)", siteID: siteId)
}

static func chartSummary(unit: String, siteId: NSNumber) -> CacheKey {
return .init(record: .subscribersChart, key: unit, siteID: siteId)
}

static func subscribersList(quantity: Int, siteId: NSNumber) -> CacheKey {
return .init(record: .subscribersList, key: "\(quantity)", siteID: siteId)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

import UIKit

class StatsSubscribersChartCell: StatsBaseCell, NibLoadable {
private typealias Style = WPStyleGuide.Stats

@IBOutlet weak var chartView: UIView!

private var chartData: LineChartDataConvertible!
private var chartStyling: LineChartStyling!
private var xAxisDates: [Date]!

override func awakeFromNib() {
super.awakeFromNib()

Style.configureCell(self)
}

func configure(row: SubscriberChartRow) {
statSection = row.statSection

self.chartData = row.chartData
self.chartStyling = row.chartStyling
self.xAxisDates = row.xAxisDates

configureChartView()
}
}

private extension StatsSubscribersChartCell {

func configureChartView() {
let configuration = StatsLineChartConfiguration(data: chartData,
styling: chartStyling,
analyticsGranularity: .days,
indexToHighlight: 0,
xAxisDates: xAxisDates)
let lineChartView = StatsLineChartView(configuration: configuration)

resetChartContainerView()
chartView.addSubview(lineChartView)
chartView.accessibilityElements = [lineChartView]

NSLayoutConstraint.activate([
lineChartView.leadingAnchor.constraint(equalTo: chartView.leadingAnchor),
lineChartView.trailingAnchor.constraint(equalTo: chartView.trailingAnchor),
lineChartView.topAnchor.constraint(equalTo: chartView.topAnchor),
lineChartView.bottomAnchor.constraint(equalTo: chartView.bottomAnchor)
])
}

func resetChartContainerView() {
for subview in chartView.subviews {
subview.removeFromSuperview()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="218" id="KGk-i7-Jjw" customClass="StatsSubscribersChartCell" customModule="WordPress" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="218"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="218"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="evm-eb-QVW">
<rect key="frame" x="16" y="0.0" width="288" height="202"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
<constraints>
<constraint firstItem="evm-eb-QVW" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="16" id="Iz4-zu-uPc"/>
<constraint firstAttribute="trailing" secondItem="evm-eb-QVW" secondAttribute="trailing" constant="16" id="f4y-nV-AaL"/>
<constraint firstItem="evm-eb-QVW" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" id="gx5-HF-joq"/>
<constraint firstAttribute="bottom" secondItem="evm-eb-QVW" secondAttribute="bottom" constant="16" id="wjN-Op-hhS"/>
</constraints>
</tableViewCellContentView>
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
<connections>
<outlet property="chartView" destination="evm-eb-QVW" id="Vkn-bP-b8X"/>
<outlet property="topConstraint" destination="gx5-HF-joq" id="84x-I3-WDn"/>
</connections>
<point key="canvasLocation" x="138.93129770992365" y="80.985915492957744"/>
</tableViewCell>
</objects>
</document>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Foundation
import DGCharts

private struct SubscriberLineChartData: LineChartDataConvertible {
let accessibilityDescription: String
let lineChartData: LineChartData
}

class StatsSubscribersLineChart {

let lineChartData: LineChartDataConvertible
let lineChartStyling: LineChartStyling

init(counts: [Int]) {
let chartEntries = counts.enumerated().map { index, count in
ChartDataEntry(x: Double(index), y: Double(count))
}
let dataSet = LineChartDataSet(entries: chartEntries)
let chartData = LineChartData(dataSets: [dataSet])
lineChartData = SubscriberLineChartData(accessibilityDescription: "Subscriber Charts", lineChartData: chartData)
lineChartStyling = SubscribersLineChartStyling()
}
}

// MARK: - StatsSubscribersLineChartStyling

private struct SubscribersLineChartStyling: LineChartStyling {
let primaryLineColor: UIColor = UIColor(light: .muriel(name: .blue, .shade50), dark: .muriel(name: .blue, .shade50))
guarani marked this conversation as resolved.
Show resolved Hide resolved
let secondaryLineColor: UIColor? = nil
let primaryHighlightColor: UIColor? = UIColor(red: 209.0/255.0, green: 209.0/255.0, blue: 214.0/255.0, alpha: 1.0)
let labelColor: UIColor = UIColor(light: .secondaryLabel, dark: .tertiaryLabel)
let legendColor: UIColor? = nil
let legendTitle: String? = nil
let lineColor: UIColor = .neutral(.shade5)
let yAxisValueFormatter: AxisValueFormatter = VerticalAxisFormatter()
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import WordPressKit

protocol StatsSubscribersStoreProtocol {
var emailsSummary: CurrentValueSubject<StatsSubscribersStore.State<StatsEmailsSummaryData>, Never> { get }
var chartSummary: CurrentValueSubject<StatsSubscribersStore.State<StatsSubscribersSummaryData>, Never> { get }
var subscribersList: CurrentValueSubject<StatsSubscribersStore.State<[StatsFollower]>, Never> { get }

func updateEmailsSummary(quantity: Int, sortField: StatsEmailsSummaryData.SortField)
func updateChartSummary()
func updateSubscribersList(quantity: Int)
}

Expand All @@ -16,6 +18,7 @@ struct StatsSubscribersStore: StatsSubscribersStoreProtocol {
private let statsService: StatsServiceRemoteV2

var emailsSummary: CurrentValueSubject<State<StatsEmailsSummaryData>, Never> = .init(.idle)
var chartSummary: CurrentValueSubject<State<StatsSubscribersSummaryData>, Never> = .init(.idle)
var subscribersList: CurrentValueSubject<State<[StatsFollower]>, Never> = .init(.idle)

init() {
Expand Down Expand Up @@ -55,6 +58,34 @@ struct StatsSubscribersStore: StatsSubscribersStoreProtocol {
}
}

func updateChartSummary() {
guard chartSummary.value != .loading else { return }

let unit = StatsPeriodUnit.day
let cacheKey = StatsSubscribersCache.CacheKey.chartSummary(unit: unit.stringValue, siteId: siteID)
let cachedData: StatsSubscribersSummaryData? = cache.getValue(key: cacheKey)

if let cachedData = cachedData {
self.chartSummary.send(.success(cachedData))
} else {
chartSummary.send(.loading)
}

statsService.getData(for: unit, endingOn: StatsDataHelper.currentDateForSite(), limit: 30) { (data: StatsSubscribersSummaryData?, error: Error?) in
DispatchQueue.main.async {
if let data = data {
cache.setValue(data, key: cacheKey)
self.chartSummary.send(.success(data))
}
else {
if cachedData == nil {
self.chartSummary.send(.error)
}
}
}
}
}

// MARK: - Subscribers List

func updateSubscribersList(quantity: Int) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ final class StatsSubscribersViewController: SiteStatsBaseTableViewController {

func tableRowTypes() -> [ImmuTableRow.Type] {
return [
SubscriberChartRow.self,
TopTotalsPeriodStatsRow.self,
StatsGhostTopImmutableRow.self,
StatsErrorRow.self
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ final class StatsSubscribersViewModel {
}

func refreshData() {
store.updateChartSummary()
store.updateEmailsSummary(quantity: 10, sortField: .postId)
store.updateSubscribersList(quantity: 10)
}

// MARK: - Lifecycle

func addObservers() {
Publishers.CombineLatest(
Publishers.CombineLatest3(
store.chartSummary.removeDuplicates(),
store.emailsSummary.removeDuplicates(),
store.subscribersList.removeDuplicates()
)
Expand All @@ -41,6 +43,7 @@ final class StatsSubscribersViewModel {
private extension StatsSubscribersViewModel {
func updateTableViewSnapshot() {
var snapshot = ImmuTableDiffableDataSourceSnapshot()
snapshot.addSection(chartRows())
snapshot.addSection(subscribersListRows())
snapshot.addSection(emailsSummaryRows())
tableViewSnapshot.send(snapshot)
Expand All @@ -55,6 +58,31 @@ private extension StatsSubscribersViewModel {
}
}

// MARK: - Chart

private extension StatsSubscribersViewModel {
func chartRows() -> [any StatsHashableImmuTableRow] {
switch store.chartSummary.value {
case .loading, .idle:
return loadingRows(.subscribersChart)
case .success(let chartSummary):
let xAxisDates = chartSummary.history.map { $0.date }
let viewsChart = StatsSubscribersLineChart(counts: chartSummary.history.map { $0.count })
return [
SubscriberChartRow(
history: chartSummary.history,
chartData: viewsChart.lineChartData,
chartStyling: viewsChart.lineChartStyling,
xAxisDates: xAxisDates,
statSection: .subscribersChart
)
]
case .error:
return errorRows(.subscribersChart)
}
}
}

// MARK: - Emails Summary

private extension StatsSubscribersViewModel {
Expand Down
Loading