Skip to content

Commit

Permalink
Merge pull request #5084 from wikimedia/T376069
Browse files Browse the repository at this point in the history
  • Loading branch information
tonisevener authored Nov 22, 2024
2 parents 32ccdd1 + a143a2d commit 0613f61
Show file tree
Hide file tree
Showing 11 changed files with 290 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,24 @@ public final class WMFPageViewCount: Identifiable {
}
}

public final class WMFPageViewDay: Decodable, Encodable {
public let day: Int
public let viewCount: Int

public init(day: Int, viewCount: Int) {
self.day = day
self.viewCount = viewCount
}

public func getViewCount() -> Int {
viewCount
}

public func getDay() -> Int {
day
}
}

public final class WMFPageViewImportRequest {
let title: String
let project: WMFProject
Expand Down Expand Up @@ -75,7 +93,7 @@ public final class WMFPageViewsDataController {
let viewedPage = try self.coreDataStore.create(entityType: CDPageView.self, in: backgroundContext)
viewedPage.page = page
viewedPage.timestamp = currentDate

try self.coreDataStore.saveIfNeeded(moc: backgroundContext)
}
}
Expand Down Expand Up @@ -116,7 +134,7 @@ public final class WMFPageViewsDataController {
let batchPageViewDeleteRequest = NSBatchDeleteRequest(fetchRequest: pageViewFetchRequest)
batchPageViewDeleteRequest.resultType = .resultTypeObjectIDs
_ = try backgroundContext.execute(batchPageViewDeleteRequest) as? NSBatchDeleteResult

backgroundContext.refreshAllObjects()
}
}
Expand Down Expand Up @@ -165,7 +183,7 @@ public final class WMFPageViewsDataController {
}

guard let page = context.object(with: objectID) as? CDPage,
let projectID = page.projectID, let title = page.title else {
let projectID = page.projectID, let title = page.title else {
continue
}

Expand All @@ -178,4 +196,41 @@ public final class WMFPageViewsDataController {

return results
}

public func fetchPageViewDates(startDate: Date, endDate: Date, moc: NSManagedObjectContext? = nil) throws -> [WMFPageViewDay] {
let context: NSManagedObjectContext
if let moc {
context = moc
} else {
context = try coreDataStore.viewContext
}

let results: [WMFPageViewDay] = try context.performAndWait {
let predicate = NSPredicate(format: "timestamp >= %@ && timestamp <= %@", startDate as CVarArg, endDate as CVarArg)
let cdPageViews = try self.coreDataStore.fetch(entityType: CDPageView.self, predicate: predicate, fetchLimit: nil, in: context)

guard let cdPageViews = cdPageViews else {
return []
}

var countsDictionary: [Int: Int] = [:]

for cdPageView in cdPageViews {
if let timestamp = cdPageView.timestamp {
let calendar = Calendar.current
let dayOfWeek = calendar.component(.weekday, from: timestamp) // Sunday = 1, Monday = 2, ..., Saturday = 7

countsDictionary[dayOfWeek, default: 0] += 1
}
}

return countsDictionary.sorted(by: { $0.key < $1.key }).map { dayOfWeek, count in
WMFPageViewDay(day: dayOfWeek, viewCount: count)
}
}

return results
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ import CoreData
return false
}


// Check remote valid country codes
let uppercaseConfigCountryCodes = yirConfig.countryCodes.map { $0.uppercased() }
guard uppercaseConfigCountryCodes.contains(countryCode.uppercased()) else {
Expand Down Expand Up @@ -176,6 +175,11 @@ import CoreData
case .donateCount:
// Do nothing, this slide should not contribute to the personalized slide count
break
case .mostReadDay:
if yirConfig.personalizedSlides.mostReadDay.isEnabled,
slide.display == true {
personalizedSlideCount += 1
}
}
}

Expand Down Expand Up @@ -281,7 +285,7 @@ import CoreData

let backgroundContext = try coreDataStore.newBackgroundContext

let result: (report: CDYearInReviewReport, needsReadingPopulation: Bool, needsEditingPopulation: Bool, needsDonatingPopulation: Bool)? = try await backgroundContext.perform { [weak self] in
let result: (report: CDYearInReviewReport, needsReadingPopulation: Bool, needsEditingPopulation: Bool, needsDonatingPopulation: Bool, needsDayPopulation: Bool)? = try await backgroundContext.perform { [weak self] in
return try self?.getYearInReviewReportAndDataPopulationFlags(year: year, backgroundContext: backgroundContext, project: primaryAppLanguageProject, username: username)
}

Expand Down Expand Up @@ -312,10 +316,16 @@ import CoreData
}
}

if result.needsDayPopulation == true {
try await backgroundContext.perform { [weak self] in
try self?.populateDaySlide(report: report, backgroundContext: backgroundContext)
}
}

return WMFYearInReviewReport(cdReport: report)
}

private func getYearInReviewReportAndDataPopulationFlags(year: Int, backgroundContext: NSManagedObjectContext, project: WMFProject?, username: String?) throws -> (report: CDYearInReviewReport, needsReadingPopulation: Bool, needsEditingPopulation: Bool, needsDonatingPopulation: Bool)? {
private func getYearInReviewReportAndDataPopulationFlags(year: Int, backgroundContext: NSManagedObjectContext, project: WMFProject?, username: String?) throws -> (report: CDYearInReviewReport, needsReadingPopulation: Bool, needsEditingPopulation: Bool, needsDonatingPopulation: Bool, needsDayPopulation: Bool)? {
let predicate = NSPredicate(format: "year == %d", year)
let cdReport = try self.coreDataStore.fetchOrCreate(entityType: CDYearInReviewReport.self, predicate: predicate, in: backgroundContext)

Expand All @@ -342,6 +352,7 @@ import CoreData
var needsReadingPopulation = false
var needsEditingPopulation = false
var needsDonatingPopulation = false
var needsDayPopulation = false

for slide in cdSlides {
switch slide.id {
Expand All @@ -350,7 +361,6 @@ import CoreData
if slide.evaluated == false && yirConfig.personalizedSlides.readCount.isEnabled {
needsReadingPopulation = true
}

case WMFYearInReviewPersonalizedSlideID.editCount.rawValue:
if slide.evaluated == false && yirConfig.personalizedSlides.editCount.isEnabled && username != nil {
needsEditingPopulation = true
Expand All @@ -359,12 +369,16 @@ import CoreData
if slide.evaluated == false && yirConfig.personalizedSlides.donateCount.isEnabled {
needsDonatingPopulation = true
}
case WMFYearInReviewPersonalizedSlideID.mostReadDay.rawValue:
if slide.evaluated == false && yirConfig.personalizedSlides.mostReadDay.isEnabled {
needsDayPopulation = true
}
default:
debugPrint("Unrecognized Slide ID")
}
}

return (report: cdReport, needsReadingPopulation: needsReadingPopulation, needsEditingPopulation: needsEditingPopulation, needsDonatingPopulation: needsDonatingPopulation)
return (report: cdReport, needsReadingPopulation: needsReadingPopulation, needsEditingPopulation: needsEditingPopulation, needsDonatingPopulation: needsDonatingPopulation, needsDayPopulation: needsDayPopulation)
}

func initialSlides(year: Int, moc: NSManagedObjectContext) throws -> Set<CDYearInReviewSlide> {
Expand Down Expand Up @@ -394,6 +408,14 @@ import CoreData
donateCountSlide.display = false
donateCountSlide.data = nil
results.insert(donateCountSlide)

let mostReadDaySlide = try coreDataStore.create(entityType: CDYearInReviewSlide.self, in: moc)
mostReadDaySlide.year = 2024
mostReadDaySlide.id = WMFYearInReviewPersonalizedSlideID.mostReadDay.rawValue
mostReadDaySlide.evaluated = false
mostReadDaySlide.display = false
mostReadDaySlide.data = nil
results.insert(mostReadDaySlide)
}

return results
Expand Down Expand Up @@ -487,6 +509,51 @@ import CoreData
try coreDataStore.saveIfNeeded(moc: backgroundContext)
}

private func populateDaySlide(report: CDYearInReviewReport, backgroundContext: NSManagedObjectContext) throws {
guard let iosFeatureConfig = developerSettingsDataController.loadFeatureConfig()?.ios.first,
let yirConfig = iosFeatureConfig.yir(yearID: targetConfigYearID) else {
return
}

guard let dataPopulationStartDate = yirConfig.dataPopulationStartDate,
let dataPopulationEndDate = yirConfig.dataPopulationEndDate else {
return
}

let pageViewsDataController = try WMFPageViewsDataController(coreDataStore: coreDataStore)
let pageViews = try pageViewsDataController.fetchPageViewDates(startDate: dataPopulationStartDate, endDate: dataPopulationEndDate)

guard let mostPopularDay = pageViews.max(by: { $0.viewCount < $1.viewCount }) else {
return
}

guard let slides = report.slides as? Set<CDYearInReviewSlide> else {
return
}

for slide in slides {
guard let slideID = slide.id else {
continue
}

switch slideID {
case WMFYearInReviewPersonalizedSlideID.mostReadDay.rawValue:
let encoder = JSONEncoder()
slide.data = try encoder.encode(mostPopularDay)

if mostPopularDay.viewCount > 0 {
slide.display = true
}

slide.evaluated = true
default:
break
}
}

try coreDataStore.saveIfNeeded(moc: backgroundContext)
}

private func populateDonatingSlide(report: CDYearInReviewReport, backgroundContext: NSManagedObjectContext) throws {

guard let iosFeatureConfig = developerSettingsDataController.loadFeatureConfig()?.ios.first,
Expand Down Expand Up @@ -616,7 +683,6 @@ import CoreData
return report
}


public func fetchYearInReviewReports() async throws -> [WMFYearInReviewReport] {
let viewContext = try coreDataStore.viewContext
let reports: [WMFYearInReviewReport] = try await viewContext.perform {
Expand Down Expand Up @@ -656,10 +722,11 @@ import CoreData
return .editCount
case "donateCount":
return .donateCount
case "mostReadDay":
return .mostReadDay
default:
return nil
}

}

public func deleteYearInReviewReport(year: Int) async throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public struct WMFFeatureConfigResponse: Codable {
let readCount: SlideSettings
let editCount: SlideSettings
let donateCount: SlideSettings
let mostReadDay: SlideSettings
}

public struct SlideSettings: Codable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public enum WMFYearInReviewPersonalizedSlideID: String, Comparable {
case readCount
case editCount
case donateCount
case mostReadDay

public static func < (lhs: WMFYearInReviewPersonalizedSlideID, rhs: WMFYearInReviewPersonalizedSlideID) -> Bool {
return lhs.rawValue < rhs.rawValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"en"
],
"dataPopulationStartDateString": "2024-01-01T00:00:00Z",
"dataPopulationEndDateString": "2024-12-31T23:59:59Z",
"dataPopulationEndDateString": "2024-11-01T00:00:00Z",
"personalizedSlides": {
"readCount": {
"isEnabled": true
Expand All @@ -27,6 +27,9 @@
},
"donateCount": {
"isEnabled": true
},
"mostReadDay": {
"isEnabled": true
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ fileprivate class WMFMockYearInReviewDataController: WMFYearInReviewDataControll
let readCountSlideSettings = WMFFeatureConfigResponse.IOS.YearInReview.SlideSettings(isEnabled: true)
let editCountSlideSettings = WMFFeatureConfigResponse.IOS.YearInReview.SlideSettings(isEnabled: true)
let donateCountSlideSettings = WMFFeatureConfigResponse.IOS.YearInReview.SlideSettings(isEnabled: true)
let personalizedSlides = WMFFeatureConfigResponse.IOS.YearInReview.PersonalizedSlides(readCount: readCountSlideSettings, editCount: editCountSlideSettings, donateCount: donateCountSlideSettings)
let mostReadDaySlideSettings = WMFFeatureConfigResponse.IOS.YearInReview.SlideSettings(isEnabled: true)
let personalizedSlides = WMFFeatureConfigResponse.IOS.YearInReview.PersonalizedSlides(readCount: readCountSlideSettings, editCount: editCountSlideSettings, donateCount: donateCountSlideSettings, mostReadDay: mostReadDaySlideSettings)
let yearInReview = WMFFeatureConfigResponse.IOS.YearInReview(yearID: "2024.2", isEnabled: false, countryCodes: ["FR", "IT"], primaryAppLanguageCodes: ["fr", "it"], dataPopulationStartDateString: "2024-01-01T00:00:00Z", dataPopulationEndDateString: "2024-11-01T00:00:00Z", personalizedSlides: personalizedSlides)
let ios = WMFFeatureConfigResponse.IOS(version: 1, yir: [yearInReview])
let config = WMFFeatureConfigResponse(ios: [ios])
Expand Down
Loading

0 comments on commit 0613f61

Please sign in to comment.