Skip to content

Commit

Permalink
feat: Calendar Sync Feature Analytics Implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
shafqat-muneer committed Apr 4, 2024
1 parent 0f00f73 commit 97fb26d
Show file tree
Hide file tree
Showing 13 changed files with 305 additions and 19 deletions.
50 changes: 49 additions & 1 deletion Core/Core/Analytics/CoreAnalytics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ public enum AnalyticsEvent: String {
case courseOutlineDiscussionTabClicked = "Course:Discussion Tab"
case courseOutlineHandoutsTabClicked = "Course:Handouts Tab"
case datesComponentClicked = "Dates:Course Component Clicked"
case datesCalendarSyncToggle = "Dates:CalendarSync Toggle"
case datesCalendarSyncDialogAction = "Dates:CalendarSync Dialog Action"
case datesCalendarSyncSnackbar = "Dates:CalendarSync Snackbar"
case plsBannerViewed = "PLS:Banner Viewed"
case plsShiftDatesClicked = "PLS:Shift Button Clicked"
case plsShiftDatesSuccess = "PLS:Shift Dates Success"
Expand Down Expand Up @@ -157,7 +160,10 @@ public enum EventBIValue: String {
case cookiePolicyClicked = "edx.bi.app.profile.cookie_policy.clicked"
case profileDeleteAccountClicked = "edx.bi.app.profile.delete_account.clicked"
case userLogout = "edx.bi.app.user.logout"
case datesComponentClicked = "edx.bi.app.coursedates.component.clicked"
case datesComponentClicked = "edx.bi.app.dates.component.clicked"
case datesCalendarSyncToggle = "edx.bi.app.dates.calendar_sync.toggle"
case datesCalendarSyncDialogAction = "edx.bi.app.dates.calendar_sync.dialog_action"
case datesCalendarSyncSnackbar = "edx.bi.app.dates.calendar_sync.snackbar"
case plsBannerViewed = "edx.bi.app.dates.pls_banner.viewed"
case plsShiftDatesClicked = "edx.bi.app.dates.pls_banner.shift_dates.clicked"
case plsShiftDatesSuccess = "edx.bi.app.dates.pls_banner.shift_dates.success"
Expand Down Expand Up @@ -233,6 +239,10 @@ public struct EventParamKey {
public static let noOfVideos = "number_of_videos"
public static let supported = "supported"
public static let conversion = "conversion"
public static let userType = "user_type"
public static let pacing = "pacing"
public static let dialog = "dialog"
public static let snackbar = "snackbar"
}

public struct EventCategory {
Expand All @@ -244,3 +254,41 @@ public struct EventCategory {
public static let video = "video"
public static let course = "course"
}

public enum EnrollmentMode: String {
case audit
case verified
case none
}

public enum Pacing: String {
case `self` = "self"
case instructor = "instructor"
}

public enum Action: String {
case on = "on"
case off = "off"
case allow = "Allow"
case doNotAllow = "donot_allow"
case ok = "OK"
case cancel = "Cancel"
case update = "Update"
case remove = "Remove"
case done = "Done"
case viewEvent = "view_event"
}

public enum Dialog: String {
case permission = "Permission"
case add = "Add"
case remove = "Remove"
case update = "Update"
case confirmed = "Confirmed"
}

public enum Snackbar: String {
case add = "Add"
case remove = "Remove"
case update = "Update"
}
5 changes: 4 additions & 1 deletion Core/Core/Domain/Model/CourseBlockModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public struct CourseStructure: Equatable {
public var childs: [CourseChapter]
public let media: DataLayer.CourseMedia //FIXME Domain model
public let certificate: Certificate?
public let isSelfPaced: Bool

public init(
id: String,
Expand All @@ -33,7 +34,8 @@ public struct CourseStructure: Equatable {
topicID: String? = nil,
childs: [CourseChapter],
media: DataLayer.CourseMedia,
certificate: Certificate?
certificate: Certificate?,
isSelfPaced: Bool
) {
self.id = id
self.graded = graded
Expand All @@ -45,6 +47,7 @@ public struct CourseStructure: Equatable {
self.childs = childs
self.media = media
self.certificate = certificate
self.isSelfPaced = isSelfPaced
}

public func totalVideosSizeInBytes(downloadQuality: DownloadQuality) -> Int {
Expand Down
6 changes: 4 additions & 2 deletions Course/Course/Data/CourseRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ public class CourseRepository: CourseRepositoryProtocol {
topicID: courseBlock.userViewData?.topicID,
childs: childs,
media: course.media,
certificate: course.certificate?.domain
certificate: course.certificate?.domain,
isSelfPaced: course.isSelfPaced
)
}

Expand Down Expand Up @@ -346,7 +347,8 @@ And there are various ways of describing it-- call it oral poetry or
topicID: courseBlock.userViewData?.topicID,
childs: childs,
media: course.media,
certificate: course.certificate?.domain
certificate: course.certificate?.domain,
isSelfPaced: course.isSelfPaced
)
}

Expand Down
13 changes: 12 additions & 1 deletion Course/Course/Data/Model/Data_CourseOutlineResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,31 @@ public extension DataLayer {
public let id: String
public let media: DataLayer.CourseMedia
public let certificate: Certificate?
public let isSelfPaced: Bool

enum CodingKeys: String, CodingKey {
case blocks
case rootItem = "root"
case id
case media
case certificate
case isSelfPaced = "is_self_paced"
}

public init(rootItem: String, dict: Blocks, id: String, media: DataLayer.CourseMedia, certificate: Certificate?) {
public init(
rootItem: String,
dict: Blocks,
id: String,
media: DataLayer.CourseMedia,
certificate: Certificate?,
isSelfPaced: Bool
) {
self.rootItem = rootItem
self.dict = dict
self.id = id
self.media = media
self.certificate = certificate
self.isSelfPaced = isSelfPaced
}

public init(from decoder: Decoder) throws {
Expand All @@ -44,6 +54,7 @@ public extension DataLayer {
id = try values.decode(String.self, forKey: .id)
media = try values.decode(DataLayer.CourseMedia.self, forKey: .media)
certificate = try values.decode(Certificate.self, forKey: .certificate)
isSelfPaced = try values.decode(Bool.self, forKey: .isSelfPaced)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23D60" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23E214" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="CDCourseBlock" representedClassName="CDCourseBlock" syncable="YES" codeGenerationType="class">
<attribute name="allSources" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData" customClassName="[String]"/>
<attribute name="blockId" optional="YES" attributeType="String"/>
Expand Down Expand Up @@ -79,6 +79,7 @@
<entity name="CDCourseStructure" representedClassName="CDCourseStructure" syncable="YES" codeGenerationType="class">
<attribute name="certificate" optional="YES" attributeType="String"/>
<attribute name="id" optional="YES" attributeType="String"/>
<attribute name="isSelfPaced" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="mediaLarge" optional="YES" attributeType="String"/>
<attribute name="mediaRaw" optional="YES" attributeType="String"/>
<attribute name="mediaSmall" optional="YES" attributeType="String"/>
Expand Down
3 changes: 2 additions & 1 deletion Course/Course/Domain/CourseInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ public class CourseInteractor: CourseInteractorProtocol {
topicID: course.topicID,
childs: newChilds,
media: course.media,
certificate: course.certificate
certificate: course.certificate,
isSelfPaced: course.isSelfPaced
)
}

Expand Down
38 changes: 38 additions & 0 deletions Course/Course/Presentation/CourseAnalytics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,25 @@ public protocol CourseAnalytics {
link: String,
supported: Bool
)
func calendarSyncToggle(
userType: EnrollmentMode,
pacing: Pacing,
courseId: String,
action: Action
)
func calendarSyncDialogAction(
userType: EnrollmentMode,
pacing: Pacing,
courseId: String,
dialog: Dialog,
action: Action
)
func calendarSyncSnackbar(
userType: EnrollmentMode,
pacing: Pacing,
courseId: String,
snackbar: Snackbar
)
func trackCourseEvent(_ event: AnalyticsEvent, biValue: EventBIValue, courseID: String)
func plsEvent(
_ event: AnalyticsEvent,
Expand Down Expand Up @@ -87,6 +106,25 @@ class CourseAnalyticsMock: CourseAnalytics {
link: String,
supported: Bool
) {}
func calendarSyncToggle(
userType: EnrollmentMode,
pacing: Pacing,
courseId: String,
action: Action
) {}
func calendarSyncDialogAction(
userType: EnrollmentMode,
pacing: Pacing,
courseId: String,
dialog: Dialog,
action: Action
) {}
func calendarSyncSnackbar(
userType: EnrollmentMode,
pacing: Pacing,
courseId: String,
snackbar: Snackbar
) {}
public func trackCourseEvent(_ event: AnalyticsEvent, biValue: EventBIValue, courseID: String) {}
public func plsEvent(
_ event: AnalyticsEvent,
Expand Down
50 changes: 50 additions & 0 deletions Course/Course/Presentation/Dates/CourseDatesViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ public class CourseDatesViewModel: ObservableObject {
}
set {
if newValue {
trackCalendarSyncToggle(action: .on)
handleCalendar()
} else {
trackCalendarSyncToggle(action: .off)
showRemoveCalendarAlert()
}
}
Expand Down Expand Up @@ -217,8 +219,14 @@ extension CourseDatesViewModel {
guard let self else { return }
switch status {
case .authorized:
if previousStatus == .notDetermined {
trackCalendarSyncDialogAction(dialog: .permission, action: .allow)
}
showAddCalendarAlert()
default:
if previousStatus == .notDetermined {
trackCalendarSyncDialogAction(dialog: .permission, action: .doNotAllow)
}
isOn = false
if previousStatus == status {
self.showCalendarSettingsAlert()
Expand Down Expand Up @@ -260,11 +268,13 @@ extension CourseDatesViewModel {
),
positiveAction: CoreLocalization.Alert.accept,
onCloseTapped: { [weak self] in
self?.trackCalendarSyncDialogAction(dialog: .add, action: .cancel)
self?.router.dismiss(animated: true)
self?.isOn = false
self?.calendar.syncOn = false
},
okTapped: { [weak self] in
self?.trackCalendarSyncDialogAction(dialog: .add, action: .ok)
self?.router.dismiss(animated: true)
Task { [weak self] in
await self?.addCourseEvents()
Expand All @@ -283,11 +293,14 @@ extension CourseDatesViewModel {
),
positiveAction: CoreLocalization.Alert.accept,
onCloseTapped: { [weak self] in
self?.trackCalendarSyncDialogAction(dialog: .remove, action: .cancel)
self?.router.dismiss(animated: true)
},
okTapped: { [weak self] in
self?.trackCalendarSyncDialogAction(dialog: .remove, action: .ok)
self?.router.dismiss(animated: true)
self?.removeCourseCalendar { [weak self] _ in
self?.trackCalendarSyncSnackbar(snackbar: .remove)
self?.eventState = .removedCalendar
}

Expand All @@ -298,6 +311,7 @@ extension CourseDatesViewModel {

private func showEventsAddedSuccessAlert() {
if calendar.isModalPresented {
trackCalendarSyncSnackbar(snackbar: .add)
eventState = .addedCalendar
return
}
Expand All @@ -309,11 +323,13 @@ extension CourseDatesViewModel {
),
positiveAction: CourseLocalization.CourseDates.calendarViewEvents,
onCloseTapped: { [weak self] in
self?.trackCalendarSyncDialogAction(dialog: .confirmed, action: .done)
self?.router.dismiss(animated: true)
self?.isOn = true
self?.calendar.syncOn = true
},
okTapped: { [weak self] in
self?.trackCalendarSyncDialogAction(dialog: .confirmed, action: .viewEvent)
self?.router.dismiss(animated: true)
if let url = URL(string: "calshow://"), UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
Expand Down Expand Up @@ -350,20 +366,24 @@ extension CourseDatesViewModel {
positiveAction: CourseLocalization.CourseDates.calendarShiftPromptUpdateNow,
onCloseTapped: { [weak self] in
// Remove course calendar
self?.trackCalendarSyncDialogAction(dialog: .update, action: .remove)
self?.router.dismiss(animated: true)
self?.removeCourseCalendar { [weak self] _ in
self?.trackCalendarSyncSnackbar(snackbar: .remove)
self?.eventState = .removedCalendar
}
},
okTapped: { [weak self] in
// Update Calendar Now
self?.trackCalendarSyncDialogAction(dialog: .update, action: .update)
self?.router.dismiss(animated: true)
self?.removeCourseCalendar(trackAnalytics: false) { success in
self?.isOn = !success
self?.calendar.syncOn = false
self?.addCourseEvents(trackAnalytics: false) { [weak self] calendarEventsAdded in
self?.isOn = calendarEventsAdded
if calendarEventsAdded {
self?.trackCalendarSyncSnackbar(snackbar: .update)
self?.calendar.syncOn = calendarEventsAdded
self?.eventState = .updatedCalendar
}
Expand Down Expand Up @@ -444,3 +464,33 @@ extension CourseDatesViewModel {
)
}
}

extension CourseDatesViewModel {
private func trackCalendarSyncToggle(action: Action) {
analytics.calendarSyncToggle(
userType: .none,
pacing: courseStructure?.isSelfPaced ?? true ? .`self` : .instructor,
courseId: courseID,
action: action
)
}

private func trackCalendarSyncDialogAction(dialog: Dialog, action: Action) {
analytics.calendarSyncDialogAction(
userType: .none,
pacing: courseStructure?.isSelfPaced ?? true ? .`self` : .instructor,
courseId: courseID,
dialog: dialog,
action: action
)
}

private func trackCalendarSyncSnackbar(snackbar: Snackbar) {
analytics.calendarSyncSnackbar(
userType: .none,
pacing: courseStructure?.isSelfPaced ?? true ? .`self` : .instructor,
courseId: courseID,
snackbar: snackbar
)
}
}
Loading

0 comments on commit 97fb26d

Please sign in to comment.