diff --git a/Components/Sources/Components/Components/Suggested Edits/Image Recommendations/Survey/WKImageRecommendationsSurveyViewModel.swift b/Components/Sources/Components/Components/Suggested Edits/Image Recommendations/Survey/WKImageRecommendationsSurveyViewModel.swift index b211a5d6b0b..26cf224c8f0 100644 --- a/Components/Sources/Components/Components/Suggested Edits/Image Recommendations/Survey/WKImageRecommendationsSurveyViewModel.swift +++ b/Components/Sources/Components/Components/Suggested Edits/Image Recommendations/Survey/WKImageRecommendationsSurveyViewModel.swift @@ -60,6 +60,15 @@ public final class WKImageRecommendationsSurveyViewModel { return localizedStrings.other } } + + var otherText: String? { + switch self { + case .other(let reason): + return reason + default: + return nil + } + } var apiIdentifier: String { switch self { diff --git a/Components/Sources/Components/Components/Suggested Edits/Image Recommendations/WKImageRecommendationsBottomSheetViewController.swift b/Components/Sources/Components/Components/Suggested Edits/Image Recommendations/WKImageRecommendationsBottomSheetViewController.swift index d118c4d0c45..dba900b0ca5 100644 --- a/Components/Sources/Components/Components/Suggested Edits/Image Recommendations/WKImageRecommendationsBottomSheetViewController.swift +++ b/Components/Sources/Components/Components/Suggested Edits/Image Recommendations/WKImageRecommendationsBottomSheetViewController.swift @@ -8,13 +8,15 @@ final public class WKImageRecommendationsBottomSheetViewController: WKCanvasView public var viewModel: WKImageRecommendationsViewModel public var tooltipViewModels: [WKTooltipViewModel] = [] weak var delegate: WKImageRecommendationsDelegate? + weak var loggingDelegate: WKImageRecommendationsLoggingDelegate? private(set) var bottomSheetView: WKImageRecommendationBottomSheetView? // MARK: Lifecycle - public init(viewModel: WKImageRecommendationsViewModel, delegate: WKImageRecommendationsDelegate) { + public init(viewModel: WKImageRecommendationsViewModel, delegate: WKImageRecommendationsDelegate, loggingDelegate: WKImageRecommendationsLoggingDelegate) { self.viewModel = viewModel self.delegate = delegate + self.loggingDelegate = loggingDelegate super.init() } @@ -31,6 +33,12 @@ final public class WKImageRecommendationsBottomSheetViewController: WKCanvasView self.bottomSheetView = bottomSheetView } } + + public override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + loggingDelegate?.logBottomSheetDidAppear() + } // MARK: Methods @@ -66,6 +74,8 @@ extension WKImageRecommendationsBottomSheetViewController: WKImageRecommendation } func goToImageCommonsPage() { + + loggingDelegate?.logBottomSheetDidTapFileName() guard let currentRecommendation = viewModel.currentRecommendation, let url = URL(string: currentRecommendation.imageData.descriptionURL) else { @@ -79,22 +89,40 @@ extension WKImageRecommendationsBottomSheetViewController: WKImageRecommendation if let imageData = viewModel.currentRecommendation?.imageData, let title = viewModel.currentRecommendation?.title { self.dismiss(animated: true) { self.delegate?.imageRecommendationsUserDidTapInsertImage(viewModel: self.viewModel, title: title, with: imageData) + self.loggingDelegate?.logBottomSheetDidTapYes() } } } func didTapNoButton() { + loggingDelegate?.logBottomSheetDidTapNo() + let surveyView = WKImageRecommendationsSurveyView( viewModel: WKImageRecommendationsSurveyViewModel(localizedStrings: viewModel.localizedStrings.surveyLocalizedStrings), cancelAction: { [weak self] in + self?.loggingDelegate?.logRejectSurveyDidTapCancel() + self?.dismiss(animated: true) }, submitAction: { [weak self] reasons in - self?.viewModel.sendFeedback(editRevId: nil, accepted: false, reasons: reasons.map { $0.apiIdentifier } , caption: nil, completion: { result in + + guard let self, + let currentRecommendation = self.viewModel.currentRecommendation else { + return + } + + // Logging + let rejectionReasons = reasons.map { $0.apiIdentifier } + let otherReason = reasons.first(where: {$0.otherText != nil}) + + self.loggingDelegate?.logRejectSurveyDidTapSubmit(rejectionReasons: rejectionReasons, otherReason: otherReason?.otherText, fileName: currentRecommendation.imageData.filename, recommendationSource: currentRecommendation.imageData.source) + + // Send feedback API call + self.viewModel.sendFeedback(editRevId: nil, accepted: false, reasons: reasons.map { $0.apiIdentifier } , caption: nil, completion: { result in }) // Dismisses Survey View - self?.dismiss(animated: true, completion: { [weak self] in + self.dismiss(animated: true, completion: { [weak self] in // Dismisses Bottom Sheet self?.dismiss(animated: true, completion: { [weak self] in self?.viewModel.next { @@ -106,9 +134,13 @@ extension WKImageRecommendationsBottomSheetViewController: WKImageRecommendation let hostedView = WKComponentHostingController(rootView: surveyView) present(hostedView, animated: true) + + loggingDelegate?.logRejectSurveyDidAppear() } func didTapSkipButton() { + loggingDelegate?.logBottomSheetDidTapNotSure() + self.dismiss(animated: true) { self.viewModel.next { diff --git a/Components/Sources/Components/Components/Suggested Edits/Image Recommendations/WKImageRecommendationsViewController.swift b/Components/Sources/Components/Components/Suggested Edits/Image Recommendations/WKImageRecommendationsViewController.swift index 0e08f335265..a145df77539 100644 --- a/Components/Sources/Components/Components/Suggested Edits/Image Recommendations/WKImageRecommendationsViewController.swift +++ b/Components/Sources/Components/Components/Suggested Edits/Image Recommendations/WKImageRecommendationsViewController.swift @@ -12,6 +12,26 @@ public protocol WKImageRecommendationsDelegate: AnyObject { func imageRecommendationsUserDidTapReportIssue() } +public protocol WKImageRecommendationsLoggingDelegate: AnyObject { + func logOnboardingDidTapPrimaryButton() + func logOnboardingDidTapSecondaryButton() + func logTooltipsDidTapFirstNext() + func logTooltipsDidTapSecondNext() + func logTooltipsDidTapThirdOK() + func logBottomSheetDidAppear() + func logBottomSheetDidTapYes() + func logBottomSheetDidTapNo() + func logBottomSheetDidTapNotSure() + func logOverflowDidTapLearnMore() + func logOverflowDidTapTutorial() + func logOverflowDidTapProblem() + func logBottomSheetDidTapFileName() + func logRejectSurveyDidAppear() + func logRejectSurveyDidTapCancel() + func logRejectSurveyDidTapSubmit(rejectionReasons: [String], otherReason: String?, fileName: String, recommendationSource: String) + func logEmptyStateDidTapBack() +} + fileprivate final class WKImageRecommendationsHostingViewController: WKComponentHostingController { init(viewModel: WKImageRecommendationsViewModel, delegate: WKImageRecommendationsDelegate, tooltipGeometryValues: WKTooltipGeometryValues) { @@ -32,6 +52,7 @@ public final class WKImageRecommendationsViewController: WKCanvasViewController fileprivate let hostingViewController: WKImageRecommendationsHostingViewController private weak var delegate: WKImageRecommendationsDelegate? + private weak var loggingDelegate: WKImageRecommendationsLoggingDelegate? @ObservedObject private var viewModel: WKImageRecommendationsViewModel private var imageRecommendationBottomSheetController: WKImageRecommendationsBottomSheetViewController private var cancellables = Set() @@ -39,13 +60,16 @@ public final class WKImageRecommendationsViewController: WKCanvasViewController private var overflowMenu: UIMenu { let learnMore = UIAction(title: viewModel.localizedStrings.learnMoreButtonTitle, image: UIImage(systemName: "info.circle"), handler: { [weak self] _ in + self?.loggingDelegate?.logOverflowDidTapLearnMore() self?.goToFAQ() }) let tutorial = UIAction(title: viewModel.localizedStrings.tutorialButtonTitle, image: UIImage(systemName: "lightbulb.min"), handler: { [weak self] _ in + self?.loggingDelegate?.logOverflowDidTapTutorial() self?.showTutorial() }) let reportIssues = UIAction(title: viewModel.localizedStrings.problemWithFeatureButtonTitle, image: UIImage(systemName: "flag"), handler: { [weak self] _ in + self?.loggingDelegate?.logOverflowDidTapProblem() self?.reportIssue() }) @@ -59,11 +83,12 @@ public final class WKImageRecommendationsViewController: WKCanvasViewController private let dataController = WKImageRecommendationsDataController() private let tooltipGeometryValues = WKTooltipGeometryValues() - public init(viewModel: WKImageRecommendationsViewModel, delegate: WKImageRecommendationsDelegate) { + public init(viewModel: WKImageRecommendationsViewModel, delegate: WKImageRecommendationsDelegate, loggingDelegate: WKImageRecommendationsLoggingDelegate) { self.hostingViewController = WKImageRecommendationsHostingViewController(viewModel: viewModel, delegate: delegate, tooltipGeometryValues: tooltipGeometryValues) self.delegate = delegate + self.loggingDelegate = loggingDelegate self.viewModel = viewModel - self.imageRecommendationBottomSheetController = WKImageRecommendationsBottomSheetViewController(viewModel: viewModel, delegate: delegate) + self.imageRecommendationBottomSheetController = WKImageRecommendationsBottomSheetViewController(viewModel: viewModel, delegate: delegate, loggingDelegate: loggingDelegate) super.init() } @@ -77,6 +102,10 @@ public final class WKImageRecommendationsViewController: WKCanvasViewController navigationItem.backButtonDisplayMode = .generic setupOverflowMenu() addComponent(hostingViewController, pinToEdges: true) + + navigationController?.interactivePopGestureRecognizer?.isEnabled = false + let image = WKSFSymbolIcon.for(symbol: .chevronBackward, font: .boldBody) + navigationItem.leftBarButtonItem = UIBarButtonItem(image: image, style: .plain, target: self, action: #selector(tappedBack)) } public override func viewWillAppear(_ animated: Bool) { @@ -108,6 +137,16 @@ public final class WKImageRecommendationsViewController: WKCanvasViewController } // MARK: Private methods + + @objc private func tappedBack() { + + if viewModel.imageRecommendations.isEmpty { + loggingDelegate?.logEmptyStateDidTapBack() + } + + navigationController?.interactivePopGestureRecognizer?.isEnabled = true + navigationController?.popViewController(animated: true) + } private func setupOverflowMenu() { let rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), primaryAction: nil, menu: overflowMenu) @@ -123,6 +162,7 @@ public final class WKImageRecommendationsViewController: WKCanvasViewController bottomSheet.prefersGrabberVisible = true bottomSheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true } + navigationController?.present(imageRecommendationBottomSheetController, animated: true, completion: { DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in @@ -176,16 +216,16 @@ public final class WKImageRecommendationsViewController: WKCanvasViewController articleSummaryDivSourceRect = divGlobalFrame } - let viewModel1 = WKTooltipViewModel(localizedStrings: viewModel.localizedStrings.firstTooltipStrings, buttonNeedsDisclosure: true, sourceView: hostingView, sourceRect: articleSummaryDivSourceRect, permittedArrowDirections: .up) { - print("do some logging 1") + let viewModel1 = WKTooltipViewModel(localizedStrings: viewModel.localizedStrings.firstTooltipStrings, buttonNeedsDisclosure: true, sourceView: hostingView, sourceRect: articleSummaryDivSourceRect, permittedArrowDirections: .up) { [weak self] in + self?.loggingDelegate?.logTooltipsDidTapFirstNext() } - let viewModel2 = WKTooltipViewModel(localizedStrings: viewModel.localizedStrings.secondTooltipStrings, buttonNeedsDisclosure: true, sourceView: bottomSheetView, sourceRect: bottomSheetView.bounds) { - print("do some logging 2") + let viewModel2 = WKTooltipViewModel(localizedStrings: viewModel.localizedStrings.secondTooltipStrings, buttonNeedsDisclosure: true, sourceView: bottomSheetView, sourceRect: bottomSheetView.bounds) { [weak self] in + self?.loggingDelegate?.logTooltipsDidTapSecondNext() } - let viewModel3 = WKTooltipViewModel(localizedStrings: viewModel.localizedStrings.thirdTooltipStrings, buttonNeedsDisclosure: false, sourceView: bottomSheetView, sourceRect: bottomSheetView.toolbar.frame) { - print("do some logging 3") + let viewModel3 = WKTooltipViewModel(localizedStrings: viewModel.localizedStrings.thirdTooltipStrings, buttonNeedsDisclosure: false, sourceView: bottomSheetView, sourceRect: bottomSheetView.toolbar.frame) { [weak self] in + self?.loggingDelegate?.logTooltipsDidTapThirdOK() } bottomSheetViewController.displayTooltips(tooltipViewModels: [viewModel1, viewModel2, viewModel3]) @@ -251,6 +291,8 @@ extension WKImageRecommendationsViewController: WKOnboardingViewDelegate { } }) + + loggingDelegate?.logOnboardingDidTapPrimaryButton() } public func onboardingViewDidClickSecondaryButton() { @@ -259,6 +301,8 @@ extension WKImageRecommendationsViewController: WKOnboardingViewDelegate { } UIApplication.shared.open(url) + + loggingDelegate?.logOnboardingDidTapSecondaryButton() } public func onboardingViewWillSwipeToDismiss() { diff --git a/Components/Sources/Components/Components/Suggested Edits/Image Recommendations/WKImageRecommendationsViewModel.swift b/Components/Sources/Components/Components/Suggested Edits/Image Recommendations/WKImageRecommendationsViewModel.swift index 406b0d1254d..3dca52cd39e 100644 --- a/Components/Sources/Components/Components/Suggested Edits/Image Recommendations/WKImageRecommendationsViewModel.swift +++ b/Components/Sources/Components/Components/Suggested Edits/Image Recommendations/WKImageRecommendationsViewModel.swift @@ -82,6 +82,7 @@ public final class WKImageRecommendationsViewModel: ObservableObject { public let image: String public let filename: String public let displayFilename: String + public let source: String public let thumbUrl: String public let fullUrl: String public let description: String? @@ -90,12 +91,13 @@ public final class WKImageRecommendationsViewModel: ObservableObject { public internal(set) var uiImage: UIImage? public let wikitext: String? - public init(pageId: Int, pageTitle: String, image: String, filename: String, displayFilename: String, thumbUrl: String, fullUrl: String, description: String?, descriptionURL: String, reason: String, wikitext: String?) { + public init(pageId: Int, pageTitle: String, image: String, filename: String, displayFilename: String, source: String, thumbUrl: String, fullUrl: String, description: String?, descriptionURL: String, reason: String, wikitext: String?) { self.pageId = pageId self.pageTitle = pageTitle self.image = image self.filename = filename self.displayFilename = displayFilename + self.source = source self.thumbUrl = thumbUrl self.fullUrl = fullUrl self.description = description @@ -112,6 +114,8 @@ public final class WKImageRecommendationsViewModel: ObservableObject { @Published var articleSummary: WKArticleSummary? = nil public let imageData: WKImageRecommendationData public var caption: String? + public var altText: String? + public var suggestionAcceptDate: Date? fileprivate init(pageId: Int, title: String, articleSummary: WKArticleSummary? = nil, imageData: WKImageRecommendationData) { self.pageId = pageId @@ -322,6 +326,7 @@ public final class WKImageRecommendationsViewModel: ObservableObject { image: firstImage.image, filename: firstImage.image, displayFilename: firstImage.displayFilename, + source: firstImage.source, thumbUrl: metadata.thumbUrl, fullUrl: metadata.fullUrl, description: metadata.description, diff --git a/WKData/Sources/WKData/Models/Image Recommendations/WKImageRecommendation.swift b/WKData/Sources/WKData/Models/Image Recommendations/WKImageRecommendation.swift index 6fc419e14b0..4c69491426a 100644 --- a/WKData/Sources/WKData/Models/Image Recommendations/WKImageRecommendation.swift +++ b/WKData/Sources/WKData/Models/Image Recommendations/WKImageRecommendation.swift @@ -25,7 +25,7 @@ public struct WKImageRecommendation { public struct ImageSuggestion: Codable { public let image: String public let displayFilename: String - let source: String + public let source: String let projects: [String] public let metadata: ImageMetadata } diff --git a/WKData/Sources/WKData/Models/Shared/WKEditSummaryTag.swift b/WKData/Sources/WKData/Models/Shared/WKEditSummaryTag.swift index 6062afcc327..55ad1ad7170 100644 --- a/WKData/Sources/WKData/Models/Shared/WKEditSummaryTag.swift +++ b/WKData/Sources/WKData/Models/Shared/WKEditSummaryTag.swift @@ -12,5 +12,4 @@ public enum WKEditSummaryTag: String { case diffRollback = "#diff-rollback" case talkReply = "#talk-reply" case talkTopic = "#talk-topic" - case suggestedEditsAddImageTop = "#suggestededit-add-image-top" } diff --git a/WMF Framework/Event Platform/EventPlatformClient.swift b/WMF Framework/Event Platform/EventPlatformClient.swift index 34fed52b9ca..a455c788914 100644 --- a/WMF Framework/Event Platform/EventPlatformClient.swift +++ b/WMF Framework/Event Platform/EventPlatformClient.swift @@ -131,6 +131,7 @@ import CocoaLumberjackSwift case watchlist = "ios.watchlists" case appDonorExperience = "app_donor_experience" case editInteraction = "ios.edit_interaction" + case imageRecommendation = "android.image_recommendation_event" } /** @@ -156,6 +157,7 @@ import CocoaLumberjackSwift case editAttempt = "/analytics/legacy/editattemptstep/2.0.3" case watchlist = "/analytics/mobile_apps/ios_watchlists/4.0.0" case appInteraction = "/analytics/mobile_apps/app_interaction/1.0.0" + case imageRecommendation = "/analytics/mobile_apps/android_image_recommendation_event/1.0.0" } /** diff --git a/Wikipedia.xcodeproj/project.pbxproj b/Wikipedia.xcodeproj/project.pbxproj index 7990881be8e..37d24253f91 100644 --- a/Wikipedia.xcodeproj/project.pbxproj +++ b/Wikipedia.xcodeproj/project.pbxproj @@ -853,6 +853,9 @@ 67DAEDF027E8FB63005CF9B6 /* NotificationsCenterDetailViewModelMentionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67DAEDE527E8FB62005CF9B6 /* NotificationsCenterDetailViewModelMentionTests.swift */; }; 67DAEDF127E8FB63005CF9B6 /* NotificationsCenterDetailViewModelEditMilestoneTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67DAEDE627E8FB62005CF9B6 /* NotificationsCenterDetailViewModelEditMilestoneTests.swift */; }; 67DAEDF227E8FB63005CF9B6 /* NotificationsCenterDetailViewModelPageLinkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67DAEDE727E8FB62005CF9B6 /* NotificationsCenterDetailViewModelPageLinkTests.swift */; }; + 67DB49BE2BD1CED9006E64CE /* ImageRecommendationsFunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67DB49BD2BD1CED9006E64CE /* ImageRecommendationsFunnel.swift */; }; + 67DB49BF2BD1CED9006E64CE /* ImageRecommendationsFunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67DB49BD2BD1CED9006E64CE /* ImageRecommendationsFunnel.swift */; }; + 67DB49C02BD1CED9006E64CE /* ImageRecommendationsFunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67DB49BD2BD1CED9006E64CE /* ImageRecommendationsFunnel.swift */; }; 67DC5BE323A017CA00B03A84 /* ArticleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67DC5BE223A017CA00B03A84 /* ArticleViewController.swift */; }; 67DC5BE423A017CA00B03A84 /* ArticleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67DC5BE223A017CA00B03A84 /* ArticleViewController.swift */; }; 67DC5BE523A017CA00B03A84 /* ArticleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67DC5BE223A017CA00B03A84 /* ArticleViewController.swift */; }; @@ -3461,6 +3464,7 @@ 67DAEDE527E8FB62005CF9B6 /* NotificationsCenterDetailViewModelMentionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationsCenterDetailViewModelMentionTests.swift; sourceTree = ""; }; 67DAEDE627E8FB62005CF9B6 /* NotificationsCenterDetailViewModelEditMilestoneTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationsCenterDetailViewModelEditMilestoneTests.swift; sourceTree = ""; }; 67DAEDE727E8FB62005CF9B6 /* NotificationsCenterDetailViewModelPageLinkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationsCenterDetailViewModelPageLinkTests.swift; sourceTree = ""; }; + 67DB49BD2BD1CED9006E64CE /* ImageRecommendationsFunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRecommendationsFunnel.swift; sourceTree = ""; }; 67DC5BE223A017CA00B03A84 /* ArticleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleViewController.swift; sourceTree = ""; usesTabs = 0; }; 67DC5BE823A03FE700B03A84 /* ArticleToolbarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleToolbarController.swift; sourceTree = ""; usesTabs = 0; }; 67DC5BEE23A1427C00B03A84 /* ActionHandlerScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionHandlerScript.swift; sourceTree = ""; }; @@ -5917,6 +5921,7 @@ 6785FCCA2A6840880078FAF2 /* WatchlistFunnel.swift */, 673C55CA2ACC684E00E2EBF6 /* AppInteractionFunnel.swift */, 676F7D422B72D8B0006C211F /* EditInteractionFunnel.swift */, + 67DB49BD2BD1CED9006E64CE /* ImageRecommendationsFunnel.swift */, ); name = "Wikimedia Event Logging"; sourceTree = ""; @@ -10329,6 +10334,7 @@ 7A9F060D2266425700856321 /* InsertMediaSettingsViewController.swift in Sources */, 007F5C6D275AA74200E4B02C /* StackedImageLabelView.swift in Sources */, 0042811D25E6E841004945B3 /* NYTPhotoTransitionAnimator.m in Sources */, + 67DB49BE2BD1CED9006E64CE /* ImageRecommendationsFunnel.swift in Sources */, B09CE59A222F623900067D2A /* WKWebView+EditSelectionJavascript.swift in Sources */, B0EF42D01C43FDD200D125A8 /* UIApplicationShortcutItem+WMFShortcutItem.m in Sources */, B0F92C821E3FFEB900B72802 /* WMFAuthAccountCreationInfoFetcher.swift in Sources */, @@ -11075,6 +11081,7 @@ D8B166861FD97A0500097D8B /* ViewController.swift in Sources */, B0421AA3206991F500C22630 /* SavedTabBarItemProgressBadgeManager.swift in Sources */, 7A6ED52220ADBF950001849F /* LoginFunnel.swift in Sources */, + 67DB49C02BD1CED9006E64CE /* ImageRecommendationsFunnel.swift in Sources */, 83CA612B20D1675800EF0C4A /* ExploreCardViewController.swift in Sources */, 8356115F28D4FCEC00E95E6E /* NSMutableAttributedString+RemoveNewLine.swift in Sources */, 6798332A22C3F2940073CE6F /* UITextView+Extensions.swift in Sources */, @@ -11635,6 +11642,7 @@ 6771299524FF775E00E89CA5 /* ArticleAsLivingDocLargeEventCollectionViewCell.swift in Sources */, D8EC3E1B1E9BDA35006712EB /* (null) in Sources */, B0C7A0851F710EB1008415E7 /* WMFWelcomeAnalyticsAnimationBackgroundView.swift in Sources */, + 67DB49BF2BD1CED9006E64CE /* ImageRecommendationsFunnel.swift in Sources */, D8EC3E1D1E9BDA35006712EB /* WMFTitleInsetRespectingButton.m in Sources */, 8356115E28D4FCEC00E95E6E /* NSMutableAttributedString+RemoveNewLine.swift in Sources */, 6798332B22C3F2950073CE6F /* UITextView+Extensions.swift in Sources */, diff --git a/Wikipedia/Code/EditPreviewViewController.swift b/Wikipedia/Code/EditPreviewViewController.swift index 3b3db3d5ce2..0cbd503a680 100644 --- a/Wikipedia/Code/EditPreviewViewController.swift +++ b/Wikipedia/Code/EditPreviewViewController.swift @@ -5,6 +5,12 @@ protocol EditPreviewViewControllerDelegate: NSObjectProtocol { func editPreviewViewControllerDidTapNext(pageURL: URL, sectionID: Int?, editPreviewViewController: EditPreviewViewController) } +protocol EditPreviewViewControllerLoggingDelegate: AnyObject { + func logEditPreviewDidAppear() + func logEditPreviewDidTapBack() + func logEditPreviewDidTapNext() +} + class EditPreviewViewController: ViewController, WMFPreviewAnchorTapAlertDelegate, InternalLinkPreviewing { var sectionID: Int? var pageURL: URL @@ -14,6 +20,7 @@ class EditPreviewViewController: ViewController, WMFPreviewAnchorTapAlertDelegat var needsSimplifiedFormatToast: Bool = false weak var delegate: EditPreviewViewControllerDelegate? + weak var loggingDelegate: EditPreviewViewControllerLoggingDelegate? lazy var messagingController: ArticleWebMessagingController = { let controller = ArticleWebMessagingController() @@ -74,10 +81,13 @@ class EditPreviewViewController: ViewController, WMFPreviewAnchorTapAlertDelegat } @objc func goBack() { + loggingDelegate?.logEditPreviewDidTapBack() navigationController?.popViewController(animated: true) + } @objc func goForward() { + loggingDelegate?.logEditPreviewDidTapNext() delegate?.editPreviewViewControllerDidTapNext(pageURL: pageURL, sectionID: sectionID, editPreviewViewController: self) } @@ -111,6 +121,11 @@ class EditPreviewViewController: ViewController, WMFPreviewAnchorTapAlertDelegat super.viewWillDisappear(animated) } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + loggingDelegate?.logEditPreviewDidAppear() + } + deinit { messagingController.removeScriptMessageHandler() } diff --git a/Wikipedia/Code/EditSaveViewController.swift b/Wikipedia/Code/EditSaveViewController.swift index 75e02a236a8..63bdd03c37b 100644 --- a/Wikipedia/Code/EditSaveViewController.swift +++ b/Wikipedia/Code/EditSaveViewController.swift @@ -12,10 +12,25 @@ protocol EditSaveViewControllerDelegate: NSObjectProtocol { func editSaveViewControllerDidSave(_ editSaveViewController: EditSaveViewController, result: Result) func editSaveViewControllerWillCancel(_ saveData: EditSaveViewController.SaveData) func editSaveViewControllerDidTapShowWebPreview() - func editSaveViewControllerLogDidTapPublish(source: PageEditorViewController.Source, summaryAdded: Bool, isMinor: Bool, project: WikimediaProject) - func editSaveViewControllerLogPublishSuccess(source: PageEditorViewController.Source, revisionID: UInt64, project: WikimediaProject) - func editSaveViewControllerLogPublishFailed(source: PageEditorViewController.Source, problemSource: EditInteractionFunnel.ProblemSource?, project: WikimediaProject) - func editSaveViewControllerLogDidTapBlockedMessageLink(source: PageEditorViewController.Source, project: WikimediaProject) +} + +protocol EditSaveViewControllerPageEditorLoggingDelegate: AnyObject { + func logEditSaveViewControllerDidTapShowWebPreview() + func logEditSaveViewControllerDidTapPublish(source: PageEditorViewController.Source, summaryAdded: Bool, isMinor: Bool, project: WikimediaProject) + func logEditSaveViewControllerPublishSuccess(source: PageEditorViewController.Source, revisionID: UInt64, project: WikimediaProject) + func logEditSaveViewControllerPublishFailed(source: PageEditorViewController.Source, problemSource: EditInteractionFunnel.ProblemSource?, project: WikimediaProject) + func logEditSaveViewControllerDidTapBlockedMessageLink(source: PageEditorViewController.Source, project: WikimediaProject) +} + +protocol EditSaveViewControllerImageRecLoggingDelegate: AnyObject { + func logEditSaveViewControllerDidAppear() + func logEditSaveViewControllerDidTapBack() + func logEditSaveViewControllerDidTapMinorEditsLearnMore() + func logEditSaveViewControllerDidTapWatchlistLearnMore() + func logEditSaveViewControllerDidToggleWatchlist(isOn: Bool) + func logEditSaveViewControllerDidTapPublish(minorEditEnabled: Bool, watchlistEnabled: Bool) + func logEditSaveViewControllerPublishSuccess(revisionID: Int, summaryAdded: Bool) + func logEditSaveViewControllerLogPublishFailed(abortSource: String?) } private enum NavigationMode : Int { @@ -49,6 +64,8 @@ class EditSaveViewController: WMFScrollViewController, Themeable, UITextFieldDel var editSummaryTag: WKEditSummaryTag? var cannedSummaryTypes: [EditSummaryViewCannedButtonType] = [.typo, .grammar, .link] weak var delegate: EditSaveViewControllerDelegate? + weak var pageEditorLoggingDelegate: EditSaveViewControllerPageEditorLoggingDelegate? + weak var imageRecLoggingDelegate: EditSaveViewControllerImageRecLoggingDelegate? private lazy var captchaViewController: WMFCaptchaViewController? = WMFCaptchaViewController.wmf_initialViewControllerFromClassStoryboard() @IBOutlet private var captchaContainer: UIView! @@ -147,6 +164,9 @@ class EditSaveViewController: WMFScrollViewController, Themeable, UITextFieldDel } @objc private func goBack() { + + imageRecLoggingDelegate?.logEditSaveViewControllerDidTapBack() + delegate?.editSaveViewControllerWillCancel(SaveData(summmaryText: summaryText, isMinorEdit: minorEditToggle.isOn, shouldAddToWatchList: addToWatchlistToggle.isOn)) navigationController?.popViewController(animated: true) @@ -201,9 +221,14 @@ class EditSaveViewController: WMFScrollViewController, Themeable, UITextFieldDel addToWatchlistToggle.isOn = savedData.shouldAddToWatchList } + addToWatchlistToggle.addTarget(self, action: #selector(toggledWatchlist), for: .valueChanged) fetchWatchlistStatusAndUpdateToggle() } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + imageRecLoggingDelegate?.logEditSaveViewControllerDidAppear() + } func setupSemanticContentAttibute() { let semanticContentAttibute = MWKLanguageLinkController.semanticContentAttribute(forContentLanguageCode: languageCode) @@ -215,6 +240,10 @@ class EditSaveViewController: WMFScrollViewController, Themeable, UITextFieldDel licenseTitleTextView.semanticContentAttribute = semanticContentAttibute licenseTitleTextView.textAlignment = semanticContentAttibute == .forceRightToLeft ? .right : .left } + + @objc private func toggledWatchlist() { + imageRecLoggingDelegate?.logEditSaveViewControllerDidToggleWatchlist(isOn: addToWatchlistToggle.isOn) + } private func setupButtonsAndTitle() { navigationItem.title = WMFLocalizedString("wikitext-preview-save-changes-title", value: "Save changes", comment: "Title for edit preview screens") @@ -243,6 +272,7 @@ class EditSaveViewController: WMFScrollViewController, Themeable, UITextFieldDel let configuration = WKSmallButton.Configuration(style: .quiet) let rootView = WKSmallButton(configuration: configuration, title: WMFLocalizedString("edit-show-web-preview", languageCode: languageCode, value: "Show web preview", comment: "Title of button that will show a web preview of the edit.")) { [weak self] in self?.delegate?.editSaveViewControllerDidTapShowWebPreview() + self?.pageEditorLoggingDelegate?.logEditSaveViewControllerDidTapShowWebPreview() } let showWebPreviewButtonHostingController = UIHostingController(rootView: rootView) showWebPreviewButtonHostingController.view.translatesAutoresizingMaskIntoConstraints = false @@ -325,15 +355,20 @@ class EditSaveViewController: WMFScrollViewController, Themeable, UITextFieldDel return } + let isMinor = minorEditToggle.isOn + if let source, let pageURL, let project = WikimediaProject(siteURL: pageURL) { let summaryAdded = !summaryText.isEmpty - let isMinor = minorEditToggle.isOn - delegate?.editSaveViewControllerLogDidTapPublish(source: source, summaryAdded: summaryAdded, isMinor: isMinor, project: project) + pageEditorLoggingDelegate?.logEditSaveViewControllerDidTapPublish(source: source, summaryAdded: summaryAdded, isMinor: isMinor, project: project) } + + let isWatchlist = addToWatchlistToggle.isOn + imageRecLoggingDelegate?.logEditSaveViewControllerDidTapPublish(minorEditEnabled: isMinor, watchlistEnabled: isWatchlist) + EditAttemptFunnel.shared.logSaveAttempt(pageURL: editURL) let section: String? @@ -378,15 +413,19 @@ class EditSaveViewController: WMFScrollViewController, Themeable, UITextFieldDel return } - if let source, - let pageURL, + if let pageURL, let project = WikimediaProject(siteURL: pageURL) { - delegate?.editSaveViewControllerLogPublishSuccess(source: source, revisionID: newRevID, project: project) + if let source { + pageEditorLoggingDelegate?.logEditSaveViewControllerPublishSuccess(source: source, revisionID: newRevID, project: project) + } EditAttemptFunnel.shared.logSaveSuccess(pageURL: pageURL, revisionId: Int(newRevID)) + } + imageRecLoggingDelegate?.logEditSaveViewControllerPublishSuccess(revisionID: Int(newRevID), summaryAdded: !summaryText.isEmpty) + notifyDelegate(.success(SectionEditorChanges(newRevisionID: newRevID))) } @@ -461,7 +500,7 @@ class EditSaveViewController: WMFScrollViewController, Themeable, UITextFieldDel let pageURL, let project = WikimediaProject(siteURL: pageURL) { - delegate?.editSaveViewControllerLogDidTapBlockedMessageLink(source: source, project: project) + pageEditorLoggingDelegate?.logEditSaveViewControllerDidTapBlockedMessageLink(source: source, project: project) EditAttemptFunnel.shared.logAbort(pageURL: pageURL) } @@ -482,14 +521,18 @@ class EditSaveViewController: WMFScrollViewController, Themeable, UITextFieldDel } } - if let source, - let pageURL, + if let pageURL, let project = WikimediaProject(siteURL: pageURL) { - delegate?.editSaveViewControllerLogPublishFailed(source: source, problemSource: problemSource, project: project) + if let source { + pageEditorLoggingDelegate?.logEditSaveViewControllerPublishFailed(source: source, problemSource: problemSource, project: project) + } + EditAttemptFunnel.shared.logSaveFailure(pageURL: pageURL) } + + imageRecLoggingDelegate?.logEditSaveViewControllerLogPublishFailed(abortSource: problemSource?.rawValue) } internal func textFieldShouldReturn(_ textField: UITextField) -> Bool { @@ -555,10 +598,12 @@ class EditSaveViewController: WMFScrollViewController, Themeable, UITextFieldDel } @IBAction public func minorEditButtonTapped(sender: UIButton) { + imageRecLoggingDelegate?.logEditSaveViewControllerDidTapMinorEditsLearnMore() navigate(to: URL(string: "https://meta.wikimedia.org/wiki/Help:Minor_edit")) } @IBAction public func watchlistButtonTapped(sender: UIButton) { + imageRecLoggingDelegate?.logEditSaveViewControllerDidTapWatchlistLearnMore() navigate(to: URL(string: "https://www.mediawiki.org/wiki/Help:Watching_pages")) } diff --git a/Wikipedia/Code/ExploreFeedSettingsViewController.swift b/Wikipedia/Code/ExploreFeedSettingsViewController.swift index dcdec953d74..8ed4f70bef0 100644 --- a/Wikipedia/Code/ExploreFeedSettingsViewController.swift +++ b/Wikipedia/Code/ExploreFeedSettingsViewController.swift @@ -336,6 +336,9 @@ extension ExploreFeedSettingsViewController { assertionFailure("Content group kind \(contentGroupKind) is not customizable nor global") return } + if contentGroupKind == .suggestedEdits { + ImageRecommendationsFunnel.shared.logSettingsToggleSuggestedEditsCard(isOn: sender.isOn) + } feedContentController.toggleContentGroup(of: contentGroupKind, isOn: sender.isOn, updateFeed: false) } else { guard let language = languages.first(where: { $0.controlTag == controlTag }) else { diff --git a/Wikipedia/Code/ExploreViewController.swift b/Wikipedia/Code/ExploreViewController.swift index 0864ded82b6..94891fbcc0e 100644 --- a/Wikipedia/Code/ExploreViewController.swift +++ b/Wikipedia/Code/ExploreViewController.swift @@ -540,7 +540,11 @@ class ExploreViewController: ColumnarCollectionViewController, ExploreCardViewCo // When a random article title is tapped, show the previewed article, not another random article let useRandomArticlePreviewItem = titleAreaTapped && group.moreType == .pageWithRandomButton - if !useRandomArticlePreviewItem, let vc = group.detailViewControllerWithDataStore(dataStore, theme: theme, imageRecDelegate: self) { + if !useRandomArticlePreviewItem, let vc = group.detailViewControllerWithDataStore(dataStore, theme: theme, imageRecDelegate: self, imageRecLoggingDelegate: self) { + + if vc is WKImageRecommendationsViewController { + ImageRecommendationsFunnel.shared.logExploreCardDidTapAddImage() + } push(vc, animated: true) return @@ -673,7 +677,7 @@ class ExploreViewController: ColumnarCollectionViewController, ExploreCardViewCo func exploreCardViewController(_ exploreCardViewController: ExploreCardViewController, didSelectItemAtIndexPath indexPath: IndexPath) { guard let contentGroup = exploreCardViewController.contentGroup, - let vc = contentGroup.detailViewControllerForPreviewItemAtIndex(indexPath.row, dataStore: dataStore, theme: theme, imageRecDelegate: self) else { + let vc = contentGroup.detailViewControllerForPreviewItemAtIndex(indexPath.row, dataStore: dataStore, theme: theme, imageRecDelegate: self, imageRecLoggingDelegate: self) else { return } @@ -690,6 +694,10 @@ class ExploreViewController: ColumnarCollectionViewController, ExploreCardViewCo otdvc.initialEvent = (contentGroup.contentPreview as? [Any])?[indexPath.item] as? WMFFeedOnThisDayEvent } + if vc is WKImageRecommendationsViewController { + ImageRecommendationsFunnel.shared.logExploreCardDidTapAddImage() + } + presentedContentGroupKey = contentGroup.key switch contentGroup.detailType { case .gallery: @@ -893,12 +901,14 @@ class ExploreViewController: ColumnarCollectionViewController, ExploreCardViewCo let viewModel = WKFeatureAnnouncementViewModel(title: WMFLocalizedString("image-rec-feature-announce-title", value: "Try 'Add an image'", comment: "Title of image recommendations feature announcement modal. Displayed the first time a user lands on the Explore feed after the feature has been added (if eligible)."), body: WMFLocalizedString("image-rec-feature-announce-body", value: "Decide if an image gets added to a Wikipedia article. You can find the ‘Add an image’ card in your ‘Explore feed’.", comment: "Body of image recommendations feature announcement modal. Displayed the first time a user lands on the Explore feed after the feature has been added (if eligible)."), primaryButtonTitle: CommonStrings.tryNowTitle, image: WKIcon.addPhoto, primaryButtonAction: { [weak self] in guard let self, - let imageRecommendationViewController = WKImageRecommendationsViewController.imageRecommendationsViewController(dataStore: self.dataStore, imageRecDelegate: self) else { + let imageRecommendationViewController = WKImageRecommendationsViewController.imageRecommendationsViewController(dataStore: self.dataStore, imageRecDelegate: self, imageRecLoggingDelegate: self) else { return } navigationController?.pushViewController(imageRecommendationViewController, animated: true) + ImageRecommendationsFunnel.shared.logExploreDidTapFeatureAnnouncementPrimaryButton() + }) announceFeature(viewModel: viewModel, sourceView:view, sourceRect:sourceRect) @@ -1185,6 +1195,7 @@ extension ExploreViewController: WKImageRecommendationsDelegate { func imageRecommendationsUserDidTapImageLink(commonsURL: URL) { navigate(to: commonsURL, useSafari: false) + ImageRecommendationsFunnel.shared.logCommonsWebViewDidAppear() } func imageRecommendationsUserDidTapInsertImage(viewModel: WKImageRecommendationsViewModel, title: String, with imageData: WKImageRecommendationsViewModel.WKImageRecommendationData) { @@ -1202,7 +1213,7 @@ extension ExploreViewController: WKImageRecommendationsDelegate { if let imageURL = URL(string: imageData.descriptionURL), let thumbURL = URL(string: imageData.thumbUrl) { let searchResult = InsertMediaSearchResult(fileTitle: "File:\(imageData.filename)", displayTitle: imageData.filename, thumbnailURL: thumbURL, imageDescription: imageData.description, filePageURL: imageURL) - let insertMediaViewController = InsertMediaSettingsViewController(image: image, searchResult: searchResult, fromImageRecommendations: true, delegate: self, theme: theme) + let insertMediaViewController = InsertMediaSettingsViewController(image: image, searchResult: searchResult, fromImageRecommendations: true, delegate: self, imageRecLoggingDelegate: self, theme: theme) self.imageRecommendationsViewModel = viewModel navigationController?.pushViewController(insertMediaViewController, animated: true) } @@ -1210,7 +1221,7 @@ extension ExploreViewController: WKImageRecommendationsDelegate { } extension ExploreViewController: InsertMediaSettingsViewControllerDelegate { - func insertMediaSettingsViewControllerDidTapProgress(imageWikitext: String, caption: String?) { + func insertMediaSettingsViewControllerDidTapProgress(imageWikitext: String, caption: String?, altText: String?) { guard let viewModel = imageRecommendationsViewModel, let currentRecommendation = viewModel.currentRecommendation, @@ -1221,6 +1232,7 @@ extension ExploreViewController: InsertMediaSettingsViewControllerDelegate { } currentRecommendation.caption = caption + currentRecommendation.altText = altText do { let wikitextWithImage = try WKWikitextUtils.insertImageWikitextIntoArticleWikitextAfterTemplates(imageWikitext: imageWikitext, into: articleWikitext) @@ -1231,6 +1243,7 @@ extension ExploreViewController: InsertMediaSettingsViewControllerDelegate { editPreviewViewController.languageCode = articleURL.wmf_languageCode editPreviewViewController.wikitext = wikitextWithImage editPreviewViewController.delegate = self + editPreviewViewController.loggingDelegate = self navigationController?.pushViewController(editPreviewViewController, animated: true) } catch let error { @@ -1252,9 +1265,9 @@ extension ExploreViewController: EditPreviewViewControllerDelegate { saveVC.wikitext = editPreviewViewController.wikitext saveVC.cannedSummaryTypes = [.addedImage, .addedImageAndCaption] saveVC.needsSuppressPosting = FeatureFlags.needsImageRecommendationsSuppressPosting - saveVC.editSummaryTag = .suggestedEditsAddImageTop saveVC.delegate = self + saveVC.imageRecLoggingDelegate = self saveVC.theme = self.theme navigationController?.pushViewController(saveVC, animated: true) @@ -1342,24 +1355,180 @@ extension ExploreViewController: EditSaveViewControllerDelegate { func editSaveViewControllerDidTapShowWebPreview() { assertionFailure("This should not be called in the Image Recommendations context") } +} + +extension ExploreViewController: WKFeatureAnnouncing { - func editSaveViewControllerLogDidTapPublish(source: PageEditorViewController.Source, summaryAdded: Bool, isMinor: Bool, project: WikimediaProject) { - +} + +extension ExploreViewController: WKImageRecommendationsLoggingDelegate { + + func logOnboardingDidTapPrimaryButton() { + ImageRecommendationsFunnel.shared.logOnboardingDidTapContinue() + } + + func logOnboardingDidTapSecondaryButton() { + ImageRecommendationsFunnel.shared.logOnboardingDidTapLearnMore() + } + + func logTooltipsDidTapFirstNext() { + ImageRecommendationsFunnel.shared.logTooltipDidTapFirstNext() + } + + func logTooltipsDidTapSecondNext() { + ImageRecommendationsFunnel.shared.logTooltipDidTapSecondNext() + } + + func logTooltipsDidTapThirdOK() { + ImageRecommendationsFunnel.shared.logTooltipDidTapThirdOk() + } + + func logBottomSheetDidAppear() { + ImageRecommendationsFunnel.shared.logBottomSheetDidAppear() } - func editSaveViewControllerLogPublishSuccess(source: PageEditorViewController.Source, revisionID: UInt64, project: WikimediaProject) { + func logBottomSheetDidTapYes() { + + if let viewModel = imageRecommendationsViewModel, + let currentRecommendation = viewModel.currentRecommendation, + let siteURL = viewModel.project.siteURL, + let pageURL = siteURL.wmf_URL(withTitle: currentRecommendation.title) { + currentRecommendation.suggestionAcceptDate = Date() + EditAttemptFunnel.shared.logInit(pageURL: pageURL) + } + ImageRecommendationsFunnel.shared.logBottomSheetDidTapYes() + } + + func logBottomSheetDidTapNo() { + ImageRecommendationsFunnel.shared.logBottomSheetDidTapNo() + } + + func logBottomSheetDidTapNotSure() { + ImageRecommendationsFunnel.shared.logBottomSheetDidTapNotSure() + } + + func logOverflowDidTapLearnMore() { + ImageRecommendationsFunnel.shared.logOverflowDidTapLearnMore() + } + + func logOverflowDidTapTutorial() { + ImageRecommendationsFunnel.shared.logOverflowDidTapTutorial() } - func editSaveViewControllerLogPublishFailed(source: PageEditorViewController.Source, problemSource: EditInteractionFunnel.ProblemSource?, project: WikimediaProject) { + func logOverflowDidTapProblem() { + ImageRecommendationsFunnel.shared.logOverflowDidTapProblem() + } + + func logBottomSheetDidTapFileName() { + ImageRecommendationsFunnel.shared.logBottomSheetDidTapFileName() + } + + func logRejectSurveyDidAppear() { + ImageRecommendationsFunnel.shared.logRejectSurveyDidAppear() + } + + func logRejectSurveyDidTapCancel() { + ImageRecommendationsFunnel.shared.logRejectSurveyDidTapCancel() + } + + func logRejectSurveyDidTapSubmit(rejectionReasons: [String], otherReason: String?, fileName: String, recommendationSource: String) { + ImageRecommendationsFunnel.shared.logRejectSurveyDidTapSubmit(rejectionReasons: rejectionReasons, otherReason: otherReason, fileName: fileName, recommendationSource: recommendationSource) + } + + func logEmptyStateDidTapBack() { + ImageRecommendationsFunnel.shared.logEmptyStateDidTapBack() + } +} + +extension ExploreViewController: InsertMediaSettingsViewControllerLoggingDelegate { + func logInsertMediaSettingsViewControllerDidAppear() { + ImageRecommendationsFunnel.shared.logAddImageDetailsDidAppear() } - func editSaveViewControllerLogDidTapBlockedMessageLink(source: PageEditorViewController.Source, project: WikimediaProject) { + func logInsertMediaSettingsViewControllerDidTapFileName() { + ImageRecommendationsFunnel.shared.logAddImageDetailsDidTapFileName() + } + + func logInsertMediaSettingsViewControllerDidTapCaptionLearnMore() { + ImageRecommendationsFunnel.shared.logAddImageDetailsDidTapCaptionLearnMore() + } + + func logInsertMediaSettingsViewControllerDidTapAltTextLearnMore() { + ImageRecommendationsFunnel.shared.logAddImageDetailsDidTapAltTextLearnMore() + } + + func logInsertMediaSettingsViewControllerDidTapAdvancedSettings() { + ImageRecommendationsFunnel.shared.logAddImageDetailsDidTapAdvancedSettings() + } +} + +extension ExploreViewController: EditPreviewViewControllerLoggingDelegate { + func logEditPreviewDidAppear() { + ImageRecommendationsFunnel.shared.logPreviewDidAppear() + } + + func logEditPreviewDidTapBack() { + ImageRecommendationsFunnel.shared.logPreviewDidTapBack() + } + + func logEditPreviewDidTapNext() { + if let viewModel = imageRecommendationsViewModel, + let currentRecommendation = viewModel.currentRecommendation, + let siteURL = viewModel.project.siteURL, + let pageURL = siteURL.wmf_URL(withTitle: currentRecommendation.title) { + EditAttemptFunnel.shared.logSaveIntent(pageURL: pageURL) + } + + ImageRecommendationsFunnel.shared.logPreviewDidTapNext() } } -extension ExploreViewController: WKFeatureAnnouncing { +extension ExploreViewController: EditSaveViewControllerImageRecLoggingDelegate { + + func logEditSaveViewControllerDidAppear() { + ImageRecommendationsFunnel.shared.logSaveChangesDidAppear() + } + + func logEditSaveViewControllerDidTapBack() { + ImageRecommendationsFunnel.shared.logSaveChangesDidTapBack() + } + + func logEditSaveViewControllerDidTapMinorEditsLearnMore() { + ImageRecommendationsFunnel.shared.logSaveChangesDidTapMinorEditsLearnMore() + } + + func logEditSaveViewControllerDidTapWatchlistLearnMore() { + ImageRecommendationsFunnel.shared.logSaveChangesDidTapWatchlistLearnMore() + } + + func logEditSaveViewControllerDidToggleWatchlist(isOn: Bool) { + ImageRecommendationsFunnel.shared.logSaveChangesDidToggleWatchlist(isOn: isOn) + } + + func logEditSaveViewControllerDidTapPublish(minorEditEnabled: Bool, watchlistEnabled: Bool) { + ImageRecommendationsFunnel.shared.logSaveChangesDidTapPublish(minorEditEnabled: minorEditEnabled, watchlistEnabled: watchlistEnabled) + } + + func logEditSaveViewControllerPublishSuccess(revisionID: Int, summaryAdded: Bool) { + + guard let viewModel = imageRecommendationsViewModel, + let currentRecommendation = viewModel.currentRecommendation else { + return + } + + var timeSpent: Int? = nil + if let suggestionAcceptDate = currentRecommendation.suggestionAcceptDate { + timeSpent = Int(Date().timeIntervalSince(suggestionAcceptDate)) + } + + ImageRecommendationsFunnel.shared.logSaveChangesPublishSuccess(timeSpent: timeSpent, revisionID: revisionID, captionAdded: currentRecommendation.caption != nil, altTextAdded: currentRecommendation.altText != nil, summaryAdded: summaryAdded) + } + + func logEditSaveViewControllerLogPublishFailed(abortSource: String?) { + ImageRecommendationsFunnel.shared.logSaveChangesPublishFail(abortSource: abortSource) + } } diff --git a/Wikipedia/Code/FeedCardSettingsViewController.swift b/Wikipedia/Code/FeedCardSettingsViewController.swift index ffae3c27db1..1be730753d2 100644 --- a/Wikipedia/Code/FeedCardSettingsViewController.swift +++ b/Wikipedia/Code/FeedCardSettingsViewController.swift @@ -78,6 +78,9 @@ extension FeedCardSettingsViewController { return } guard controlTag != -1 else { // main switch + if contentGroupKind == .suggestedEdits { + ImageRecommendationsFunnel.shared.logSettingsToggleSuggestedEditsCard(isOn: sender.isOn) + } feedContentController.toggleContentGroup(of: contentGroupKind, isOn: sender.isOn, updateFeed: false) return } diff --git a/Wikipedia/Code/ImageRecommendationsFunnel.swift b/Wikipedia/Code/ImageRecommendationsFunnel.swift new file mode 100644 index 00000000000..1676d1080da --- /dev/null +++ b/Wikipedia/Code/ImageRecommendationsFunnel.swift @@ -0,0 +1,281 @@ +import Foundation + +final class ImageRecommendationsFunnel: NSObject { + + @objc static let shared = ImageRecommendationsFunnel() + + private enum ActiveInterface: String { + case onboardingStep1Dialog = "onboarding_step_1_dialog" + case onboardingStep2Dialog = "onboarding_step_2_dialog" + case onboardingStep3Dialog = "onboarding_step_3_dialog" + case onboardingStep4Dialog = "onboarding_step_4_dialog" + case onboardingStep5Dialog = "onboarding_step_5_dialog" + case suggestedEditsDialog = "suggested_edits_dialog" + case recommendedImageToolbar = "recommendedimagetoolbar" + case imageDetailsDialog = "imagedetails_dialog" + case captionEntry = "caption_entry" + case captionPreview = "caption_preview" + case editSummaryDialog = "editsummary_dialog" + case rejectionDialog = "rejection_dialog" + case noSuggestionsDialog = "nosuggestions_dialog" + case exploreSettings = "explore_settings" + } + + private enum Action: String { + case startTooltips = "start_tooltips" + case next = "next" + case learnMore = "learn_more" + case completeTooltips = "complete_tooltips" + case addImageStart = "add_image_start" + case impression = "impression" + case suggestionAccept = "suggestion_accept" + case suggestionReject = "suggestion_reject" + case suggestionSkip = "suggestion_skip" + case overflowLearnMore = "overflow_learn_more" + case overflowTutorial = "overflow_tutorial" + case overflowReport = "overflow_report" + case imageDetailView = "image_detail_view" + case viewCaptionHelp = "view_caption_help" + case viewAltTextHelp = "view_alt_text_help" + case advancedSettingOpen = "advanced_settting_open" + case captionPreviewAccept = "caption_preview_accept" + case back = "back" + case viewEditHelp = "view_edit_help" + case viewWatchlistHelp = "view_watchlist_help" + case editSummarySave = "editsummary_save" + case addWatchlist = "add_watchlist" + case removeWatchlist = "remove_watchlist" + case editSummarySuccess = "editsummary_success" + case rejectCancel = "reject_cancel" + case rejectSubmit = "reject_submit" + case noSuggestionsBack = "nosuggestions_back" + case enableSuggestedEdits = "enable_suggested_edits" + case disableSuggestedEdits = "disable_suggested_edits" + case saveFailure = "save_failure" + } + + private struct Event: EventInterface { + static let schema: EventPlatformClient.Schema = .imageRecommendation + let activeInterface: String? + let action: String? + let actionData: String? + let wikiID: String? + + enum CodingKeys: String, CodingKey { + case activeInterface = "active_interface" + case action = "action" + case actionData = "action_data" + case wikiID = "wiki_id" + } + } + + // The Image Recommendations feature is displayed only for the app primary language, so this is a shortcut to get that value. If the business logic changes to include secondary language recommendations, we will need to inject project into each method based on which wiki the recommendation is on. + + private lazy var project: WikimediaProject? = { + guard let appLanguage = MWKDataStore.shared().languageLinkController.appLanguage else { + return nil + } + + return WikimediaProject(siteURL: appLanguage.siteURL) + }() + + private func logEvent(activeInterface: ActiveInterface? = nil, action: Action? = nil, actionData: [String: String]? = nil, project: WikimediaProject? = nil) { + + var actionDataString: String? = nil + if let actionData { + actionDataString = "" + for (key, value) in actionData { + actionDataString?.append("\(key):\(value), ") + } + + // remove last ", " + if let finalActionDataString = actionDataString, + finalActionDataString.count > 1 { + actionDataString?.removeLast(2) + } + } + + let event: ImageRecommendationsFunnel.Event = ImageRecommendationsFunnel.Event(activeInterface: activeInterface?.rawValue, action: action?.rawValue, actionData: actionDataString, wikiID: project?.notificationsApiWikiIdentifier) + EventPlatformClient.shared.submit(stream: .imageRecommendation, event: event) + } + + func logExploreDidTapFeatureAnnouncementPrimaryButton() { + logEvent(activeInterface: .onboardingStep1Dialog, action: .startTooltips, project: project) + } + + func logOnboardingDidTapContinue() { + logEvent(activeInterface: .onboardingStep2Dialog, action: .next, project: project) + } + + func logOnboardingDidTapLearnMore() { + logEvent(activeInterface: .onboardingStep2Dialog, action: .learnMore, project: project) + } + + func logTooltipDidTapFirstNext() { + logEvent(activeInterface: .onboardingStep3Dialog, action: .next, project: project) + } + + func logTooltipDidTapSecondNext() { + logEvent(activeInterface: .onboardingStep4Dialog, action: .next, project: project) + } + + func logTooltipDidTapThirdOk() { + logEvent(activeInterface: .onboardingStep5Dialog, action: .completeTooltips, project: project) + } + + func logExploreCardDidTapAddImage() { + logEvent(activeInterface: .suggestedEditsDialog, action: .addImageStart, project: project) + } + + func logBottomSheetDidAppear() { + logEvent(activeInterface: .recommendedImageToolbar, action: .impression, project: project) + } + + func logBottomSheetDidTapYes() { + logEvent(activeInterface: .recommendedImageToolbar, action: .suggestionAccept, project: project) + } + + func logBottomSheetDidTapNo() { + logEvent(activeInterface: .recommendedImageToolbar, action: .suggestionReject, project: project) + } + + func logBottomSheetDidTapNotSure() { + logEvent(activeInterface: .recommendedImageToolbar, action: .suggestionSkip, project: project) + } + + func logOverflowDidTapLearnMore() { + logEvent(activeInterface: .recommendedImageToolbar, action: .overflowLearnMore, project: project) + } + + func logOverflowDidTapTutorial() { + logEvent(activeInterface: .recommendedImageToolbar, action: .overflowTutorial, project: project) + } + + func logOverflowDidTapProblem() { + logEvent(activeInterface: .recommendedImageToolbar, action: .overflowReport, project: project) + } + + func logBottomSheetDidTapFileName() { + logEvent(activeInterface: .recommendedImageToolbar, action: .imageDetailView, project: project) + } + + func logCommonsWebViewDidAppear() { + logEvent(activeInterface: .imageDetailsDialog, action: .impression, project: project) + } + + func logAddImageDetailsDidAppear() { + logEvent(activeInterface: .captionEntry, action: .impression, project: project) + } + + func logAddImageDetailsDidTapFileName() { + logEvent(activeInterface: .captionEntry, action: .imageDetailView, project: project) + } + + func logAddImageDetailsDidTapCaptionLearnMore() { + logEvent(activeInterface: .captionEntry, action: .viewCaptionHelp, project: project) + } + + func logAddImageDetailsDidTapAltTextLearnMore() { + logEvent(activeInterface: .captionEntry, action: .viewAltTextHelp, project: project) + } + + func logAddImageDetailsDidTapAdvancedSettings() { + logEvent(activeInterface: .captionEntry, action: .advancedSettingOpen, project: project) + } + + func logPreviewDidAppear() { + logEvent(activeInterface: .captionPreview, action: .impression, project: project) + } + + func logPreviewDidTapBack() { + logEvent(activeInterface: .captionPreview, action: .back, project: project) + } + + func logPreviewDidTapNext() { + logEvent(activeInterface: .captionPreview, action: .captionPreviewAccept, project: project) + } + + func logSaveChangesDidAppear() { + logEvent(activeInterface: .editSummaryDialog, action: .impression, project: project) + } + + func logSaveChangesDidTapBack() { + logEvent(activeInterface: .editSummaryDialog, action: .back, project: project) + } + + func logSaveChangesDidTapMinorEditsLearnMore() { + logEvent(activeInterface: .editSummaryDialog, action: .viewEditHelp, project: project) + } + + func logSaveChangesDidTapWatchlistLearnMore() { + logEvent(activeInterface: .editSummaryDialog, action: .viewWatchlistHelp, project: project) + } + + func logSaveChangesDidToggleWatchlist(isOn: Bool) { + let action = isOn ? Action.addWatchlist : Action.removeWatchlist + logEvent(activeInterface: .editSummaryDialog, action: action, project: project) + } + + func logSaveChangesDidTapPublish(minorEditEnabled: Bool, watchlistEnabled: Bool) { + let actionData: [String: String] = ["minor_edit": "\(minorEditEnabled)", "watchlist_added": "\(watchlistEnabled)"] + + logEvent(activeInterface: .editSummaryDialog, action: .editSummarySave, actionData:actionData, project: project) + } + + func logSaveChangesPublishSuccess(timeSpent: Int?, revisionID: Int, captionAdded: Bool, altTextAdded: Bool, summaryAdded: Bool) { + var actionData = ["revision_id": "\(revisionID)", + "capion_add": "\(captionAdded)", + "alt_text_add": "\(altTextAdded)", + "summary_add": "\(summaryAdded)"] + if let timeSpent { + actionData["time_spent"] = String(timeSpent) + + } + logEvent(activeInterface: .editSummaryDialog, action: .editSummarySuccess, actionData: actionData, project: project) + } + + func logRejectSurveyDidAppear() { + logEvent(activeInterface: .rejectionDialog, action: .impression, project: project) + } + + func logRejectSurveyDidTapCancel() { + logEvent(activeInterface: .rejectionDialog, action: .rejectCancel, project: project) + } + + func logRejectSurveyDidTapSubmit(rejectionReasons: [String], otherReason: String?, fileName: String, recommendationSource: String) { + let rejectionReasonsJoined = rejectionReasons.joined(separator: ",") + + var actionData: [String: String] = [ + "rejection_reason": "\(rejectionReasonsJoined)", + "filename": "\(fileName)", + "recommendation_source": "\(recommendationSource)" + ] + + if let otherReason { + actionData["rejection_text"] = otherReason + } + + logEvent(activeInterface: .rejectionDialog, action: .rejectSubmit, actionData: actionData, project: project) + } + + func logEmptyStateDidTapBack() { + logEvent(activeInterface: .noSuggestionsDialog, action: .noSuggestionsBack, project: project) + } + + func logSettingsToggleSuggestedEditsCard(isOn: Bool) { + let action = isOn ? Action.enableSuggestedEdits : Action.disableSuggestedEdits + logEvent(activeInterface: .exploreSettings, action: action, project: project) + } + + func logSettingsDidDisableSuggestedEditsCard() { + logEvent(activeInterface: .exploreSettings, action: .disableSuggestedEdits, project: project) + } + + func logSaveChangesPublishFail(abortSource: String?) { + var actionData: [String : String]? + if let abortSource { + actionData = ["abort_source": abortSource] + } + logEvent(activeInterface: .editSummaryDialog, action: .saveFailure, actionData: actionData, project: project) + } +} diff --git a/Wikipedia/Code/InsertMediaSettingsViewController.swift b/Wikipedia/Code/InsertMediaSettingsViewController.swift index f088dd61f4d..e45d52fe935 100644 --- a/Wikipedia/Code/InsertMediaSettingsViewController.swift +++ b/Wikipedia/Code/InsertMediaSettingsViewController.swift @@ -4,14 +4,22 @@ import WKData typealias InsertMediaSettings = InsertMediaSettingsViewController.Settings protocol InsertMediaSettingsViewControllerDelegate: ViewController { - func insertMediaSettingsViewControllerDidTapProgress(imageWikitext: String, caption: String?) + func insertMediaSettingsViewControllerDidTapProgress(imageWikitext: String, caption: String?, altText: String?) } +protocol InsertMediaSettingsViewControllerLoggingDelegate: ViewController { + func logInsertMediaSettingsViewControllerDidAppear() + func logInsertMediaSettingsViewControllerDidTapFileName() + func logInsertMediaSettingsViewControllerDidTapCaptionLearnMore() + func logInsertMediaSettingsViewControllerDidTapAltTextLearnMore() + func logInsertMediaSettingsViewControllerDidTapAdvancedSettings() +} final class InsertMediaSettingsViewController: ViewController { private let fromImageRecommendations: Bool private weak var delegate: InsertMediaSettingsViewControllerDelegate? + private weak var imageRecLoggingDelegate: InsertMediaSettingsViewControllerLoggingDelegate? private let tableView = UITableView(frame: .zero, style: .grouped) private let image: UIImage @@ -147,6 +155,7 @@ final class InsertMediaSettingsViewController: ViewController { imageView.title = searchResult.displayTitle imageView.titleURL = imageTitle imageView.titleAction = { [weak self] url in + self?.imageRecLoggingDelegate?.logInsertMediaSettingsViewControllerDidTapFileName() self?.navigate(to: url, useSafari: false) } imageView.autoresizingMask = [] @@ -163,6 +172,7 @@ final class InsertMediaSettingsViewController: ViewController { guard let self = self else { return } + imageRecLoggingDelegate?.logInsertMediaSettingsViewControllerDidTapAdvancedSettings() self.insertMediaAdvancedSettingsViewController.apply(theme: self.theme) self.navigationController?.pushViewController(self.insertMediaAdvancedSettingsViewController, animated: true) } @@ -205,11 +215,12 @@ final class InsertMediaSettingsViewController: ViewController { return [captionViewModel, alternativeTextViewModel] }() - init(image: UIImage, searchResult: InsertMediaSearchResult, fromImageRecommendations: Bool, delegate: InsertMediaSettingsViewControllerDelegate, theme: Theme) { + init(image: UIImage, searchResult: InsertMediaSearchResult, fromImageRecommendations: Bool, delegate: InsertMediaSettingsViewControllerDelegate, imageRecLoggingDelegate: InsertMediaSettingsViewControllerLoggingDelegate?, theme: Theme) { self.image = image self.searchResult = searchResult self.fromImageRecommendations = fromImageRecommendations self.delegate = delegate + self.imageRecLoggingDelegate = imageRecLoggingDelegate super.init() self.theme = theme } @@ -248,6 +259,7 @@ final class InsertMediaSettingsViewController: ViewController { let searchResult = searchResult let wikitext: String var captionToSend: String? + var altTextToSend: String? switch settings { case nil: wikitext = "[[\(searchResult.fileTitle)]]" @@ -258,6 +270,7 @@ final class InsertMediaSettingsViewController: ViewController { [[\(searchResult.fileTitle) | \(mediaSettings.advanced.imageType.rawValue) | \(mediaSettings.advanced.imageSize.rawValue) | \(mediaSettings.advanced.imagePosition.rawValue) | alt= \(alternativeText) | \(caption)]] """ captionToSend = caption + altTextToSend = alternativeText case (let caption?, nil): wikitext = """ [[\(searchResult.fileTitle) | \(mediaSettings.advanced.imageType.rawValue) | \(mediaSettings.advanced.imageSize.rawValue) | \(mediaSettings.advanced.imagePosition.rawValue) | \(caption)]] @@ -267,19 +280,22 @@ final class InsertMediaSettingsViewController: ViewController { wikitext = """ [[\(searchResult.fileTitle) | \(mediaSettings.advanced.imageType.rawValue) | \(mediaSettings.advanced.imageSize.rawValue) | \(mediaSettings.advanced.imagePosition.rawValue) | alt= \(alternativeText)]] """ + altTextToSend = alternativeText default: wikitext = """ [[\(searchResult.fileTitle) | \(mediaSettings.advanced.imageType.rawValue) | \(mediaSettings.advanced.imageSize.rawValue) | \(mediaSettings.advanced.imagePosition.rawValue)]] """ } } - delegate?.insertMediaSettingsViewControllerDidTapProgress(imageWikitext: wikitext, caption: captionToSend) + delegate?.insertMediaSettingsViewControllerDidTapProgress(imageWikitext: wikitext, caption: captionToSend, altText: altTextToSend) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) UIAccessibility.post(notification: .screenChanged, argument: nil) + + imageRecLoggingDelegate?.logInsertMediaSettingsViewControllerDidAppear() } override func viewDidLayoutSubviews() { @@ -348,8 +364,17 @@ extension InsertMediaSettingsViewController: UITableViewDataSource { guard let self = self else { return } + + switch viewModel.type { + case .caption: + self.imageRecLoggingDelegate?.logInsertMediaSettingsViewControllerDidTapCaptionLearnMore() + case .alternativeText: + self.imageRecLoggingDelegate?.logInsertMediaSettingsViewControllerDidTapAltTextLearnMore() + } + self.navigate(to: url, useSafari: false) } + return cell } diff --git a/Wikipedia/Code/InsertMediaViewController.swift b/Wikipedia/Code/InsertMediaViewController.swift index a811d3816f9..a120e15aa2a 100644 --- a/Wikipedia/Code/InsertMediaViewController.swift +++ b/Wikipedia/Code/InsertMediaViewController.swift @@ -106,7 +106,7 @@ final class InsertMediaViewController: ViewController { assertionFailure("Selected image and search result should be set by now") return } - let settingsViewController = InsertMediaSettingsViewController(image: image, searchResult: selectedSearchResult, fromImageRecommendations: false, delegate: self, theme: theme) + let settingsViewController = InsertMediaSettingsViewController(image: image, searchResult: selectedSearchResult, fromImageRecommendations: false, delegate: self, imageRecLoggingDelegate: nil, theme: theme) navigationController.pushViewController(settingsViewController, animated: true) } @@ -304,8 +304,7 @@ extension InsertMediaViewController: EditingFlowViewController { } extension InsertMediaViewController: InsertMediaSettingsViewControllerDelegate { - func insertMediaSettingsViewControllerDidTapProgress(imageWikitext: String, caption: String?) { + func insertMediaSettingsViewControllerDidTapProgress(imageWikitext: String, caption: String?, altText: String?) { delegate?.insertMediaViewController(self, didPrepareWikitextToInsert: imageWikitext) } - } diff --git a/Wikipedia/Code/PageEditorViewController.swift b/Wikipedia/Code/PageEditorViewController.swift index fed0fd5331f..189aa69d296 100644 --- a/Wikipedia/Code/PageEditorViewController.swift +++ b/Wikipedia/Code/PageEditorViewController.swift @@ -638,6 +638,7 @@ final class PageEditorViewController: UIViewController { saveVC.needsWebPreviewButton = true } saveVC.delegate = self + saveVC.pageEditorLoggingDelegate = self saveVC.theme = self.theme navigationController?.pushViewController(saveVC, animated: true) @@ -962,19 +963,14 @@ extension PageEditorViewController: EditSaveViewControllerDelegate { return } - if let project = WikimediaProject(siteURL: self.pageURL) { - switch self.source { - case .talk: - EditInteractionFunnel.shared.logTalkEditSummaryDidTapPreview(project: project) - default: - assertionFailure("Article sources should not have show web preview button on edit save.") - } - } - showEditPreview(editFlow: editFlow) } - - func editSaveViewControllerLogDidTapPublish(source: Source, summaryAdded: Bool, isMinor: Bool, project: WikimediaProject) { +} + +// MARK: - EditSaveViewControllerPageEditorLoggingDelegate + +extension PageEditorViewController: EditSaveViewControllerPageEditorLoggingDelegate { + func logEditSaveViewControllerDidTapPublish(source: Source, summaryAdded: Bool, isMinor: Bool, project: WikimediaProject) { switch source { case .article: EditInteractionFunnel.shared.logArticleEditSummaryDidTapPublish(summaryAdded: summaryAdded, minorEdit: isMinor, project: project) @@ -983,7 +979,7 @@ extension PageEditorViewController: EditSaveViewControllerDelegate { } } - func editSaveViewControllerLogPublishSuccess(source: Source, revisionID: UInt64, project: WikimediaProject) { + func logEditSaveViewControllerPublishSuccess(source: Source, revisionID: UInt64, project: WikimediaProject) { switch source { case .article: EditInteractionFunnel.shared.logArticlePublishSuccess(revisionID: Int(revisionID), project: project) @@ -992,7 +988,7 @@ extension PageEditorViewController: EditSaveViewControllerDelegate { } } - func editSaveViewControllerLogPublishFailed(source: Source, problemSource: EditInteractionFunnel.ProblemSource?, project: WikimediaProject) { + func logEditSaveViewControllerPublishFailed(source: Source, problemSource: EditInteractionFunnel.ProblemSource?, project: WikimediaProject) { switch source { case .article: EditInteractionFunnel.shared.logArticlePublishFail(problemSource: problemSource, project: project) @@ -1001,7 +997,7 @@ extension PageEditorViewController: EditSaveViewControllerDelegate { } } - func editSaveViewControllerLogDidTapBlockedMessageLink(source: Source, project: WikimediaProject) { + func logEditSaveViewControllerDidTapBlockedMessageLink(source: Source, project: WikimediaProject) { switch source { case .article: EditInteractionFunnel.shared.logArticleEditSummaryDidTapBlockedMessageLink(project: project) @@ -1009,6 +1005,17 @@ extension PageEditorViewController: EditSaveViewControllerDelegate { EditInteractionFunnel.shared.logTalkEditSummaryDidTapBlockedMessageLink(project: project) } } + + func logEditSaveViewControllerDidTapShowWebPreview() { + if let project = WikimediaProject(siteURL: self.pageURL) { + switch self.source { + case .talk: + EditInteractionFunnel.shared.logTalkEditSummaryDidTapPreview(project: project) + default: + assertionFailure("Article sources should not have show web preview button on edit save.") + } + } + } } // MARK: - EditSaveViewControllerDelegate diff --git a/Wikipedia/Code/UIViewController+FeatureComponentFactory.swift b/Wikipedia/Code/UIViewController+FeatureComponentFactory.swift index 08133c93960..31695d77182 100644 --- a/Wikipedia/Code/UIViewController+FeatureComponentFactory.swift +++ b/Wikipedia/Code/UIViewController+FeatureComponentFactory.swift @@ -3,10 +3,11 @@ import Components import WMF extension WKImageRecommendationsViewController { - static func imageRecommendationsViewController(dataStore: MWKDataStore, imageRecDelegate: WKImageRecommendationsDelegate?) -> WKImageRecommendationsViewController? { + static func imageRecommendationsViewController(dataStore: MWKDataStore, imageRecDelegate: WKImageRecommendationsDelegate?, imageRecLoggingDelegate: WKImageRecommendationsLoggingDelegate?) -> WKImageRecommendationsViewController? { guard let appLanguage = dataStore.languageLinkController.appLanguage, let project = WikimediaProject(siteURL: appLanguage.siteURL)?.wkProject, - let imageRecDelegate = imageRecDelegate else { + let imageRecDelegate, + let imageRecLoggingDelegate else { return nil } @@ -55,7 +56,7 @@ extension WKImageRecommendationsViewController { let localizedStrings = WKImageRecommendationsViewModel.LocalizedStrings(title: CommonStrings.addImageTitle, viewArticle: CommonStrings.viewArticle, onboardingStrings: onboardingStrings, surveyLocalizedStrings: surveyLocalizedStrings, emptyLocalizedStrings: emptyStrings, firstTooltipStrings: firstTooltipStrings, secondTooltipStrings: secondTooltipStrings, thirdTooltipStrings: thirdTooltipStrings, bottomSheetTitle: CommonStrings.bottomSheetTitle, yesButtonTitle: CommonStrings.yesButtonTitle, noButtonTitle: CommonStrings.noButtonTitle, notSureButtonTitle: CommonStrings.notSureButtonTitle, learnMoreButtonTitle: CommonStrings.learnMoreTitle(), tutorialButtonTitle: CommonStrings.tutorialTitle, problemWithFeatureButtonTitle: CommonStrings.problemWithFeatureTitle) let viewModel = WKImageRecommendationsViewModel(project: project, semanticContentAttribute: semanticContentAttribute, localizedStrings: localizedStrings, needsSuppressPosting: FeatureFlags.needsImageRecommendationsSuppressPosting) - let imageRecommendationsViewController = WKImageRecommendationsViewController(viewModel: viewModel, delegate: imageRecDelegate) + let imageRecommendationsViewController = WKImageRecommendationsViewController(viewModel: viewModel, delegate: imageRecDelegate, loggingDelegate: imageRecLoggingDelegate) return imageRecommendationsViewController } } diff --git a/Wikipedia/Code/WMFContentGroup+DetailViewControllers.swift b/Wikipedia/Code/WMFContentGroup+DetailViewControllers.swift index 8694719e3c6..f07fbc396f7 100644 --- a/Wikipedia/Code/WMFContentGroup+DetailViewControllers.swift +++ b/Wikipedia/Code/WMFContentGroup+DetailViewControllers.swift @@ -5,10 +5,10 @@ extension WMFContentGroup { @objc(detailViewControllerForPreviewItemAtIndex:dataStore:theme:) public func detailViewControllerForPreviewItemAtIndex(_ index: Int, dataStore: MWKDataStore, theme: Theme) -> UIViewController? { - detailViewControllerForPreviewItemAtIndex(index, dataStore: dataStore, theme: theme, imageRecDelegate: nil) + detailViewControllerForPreviewItemAtIndex(index, dataStore: dataStore, theme: theme, imageRecDelegate: nil, imageRecLoggingDelegate: nil) } - public func detailViewControllerForPreviewItemAtIndex(_ index: Int, dataStore: MWKDataStore, theme: Theme, imageRecDelegate: WKImageRecommendationsDelegate?) -> UIViewController? { + public func detailViewControllerForPreviewItemAtIndex(_ index: Int, dataStore: MWKDataStore, theme: Theme, imageRecDelegate: WKImageRecommendationsDelegate?, imageRecLoggingDelegate: WKImageRecommendationsLoggingDelegate?) -> UIViewController? { switch detailType { case .page: guard let articleURL = previewArticleURLForItemAtIndex(index) else { @@ -26,9 +26,9 @@ extension WMFContentGroup { } return WMFPOTDImageGalleryViewController(dates: [date], theme: theme, overlayViewTopBarHidden: false) case .story, .event: - return detailViewControllerWithDataStore(dataStore, theme: theme, imageRecDelegate: nil) + return detailViewControllerWithDataStore(dataStore, theme: theme, imageRecDelegate: nil, imageRecLoggingDelegate: nil) case .suggestedEdits: - return detailViewControllerWithDataStore(dataStore, theme: theme, imageRecDelegate: imageRecDelegate) + return detailViewControllerWithDataStore(dataStore, theme: theme, imageRecDelegate: imageRecDelegate, imageRecLoggingDelegate: imageRecLoggingDelegate) default: return nil } @@ -36,10 +36,10 @@ extension WMFContentGroup { @objc(detailViewControllerWithDataStore:theme:) public func detailViewControllerWithDataStore(_ dataStore: MWKDataStore, theme: Theme) -> UIViewController? { - return detailViewControllerWithDataStore(dataStore, theme: theme, imageRecDelegate: nil) + return detailViewControllerWithDataStore(dataStore, theme: theme, imageRecDelegate: nil, imageRecLoggingDelegate: nil) } - public func detailViewControllerWithDataStore(_ dataStore: MWKDataStore, theme: Theme, imageRecDelegate: WKImageRecommendationsDelegate?) -> UIViewController? { + public func detailViewControllerWithDataStore(_ dataStore: MWKDataStore, theme: Theme, imageRecDelegate: WKImageRecommendationsDelegate?, imageRecLoggingDelegate: WKImageRecommendationsLoggingDelegate?) -> UIViewController? { var vc: UIViewController? = nil switch moreType { case .pageList: @@ -71,7 +71,7 @@ extension WMFContentGroup { (firstRandom as Themeable).apply(theme: theme) vc = firstRandom case .imageRecommendations: - vc = WKImageRecommendationsViewController.imageRecommendationsViewController(dataStore: dataStore, imageRecDelegate: imageRecDelegate) + vc = WKImageRecommendationsViewController.imageRecommendationsViewController(dataStore: dataStore, imageRecDelegate: imageRecDelegate, imageRecLoggingDelegate: imageRecLoggingDelegate) default: break }