From a9e6e10e78a496edec601f383b97f55c784cb243 Mon Sep 17 00:00:00 2001 From: Gio Date: Wed, 30 Oct 2024 17:38:56 -0500 Subject: [PATCH 1/5] feat(analytics): add card impression events to SwiftUI home --- .../Analytics/AppEvents/Collection.swift | 15 +++++++++++ .../Analytics/AppEvents/ExpandedSlate.swift | 2 +- .../Sources/Analytics/AppEvents/Home.swift | 6 ++--- .../Analytics/AppEvents/SharedWithYou.swift | 2 +- .../Home/SwiftUI/Models/CardType.swift | 13 ++++++++++ .../Home/SwiftUI/Models/HomeActions.swift | 26 +++++++++++++++++++ .../Models/HomeCardConfiguration.swift | 3 +++ .../Home/SwiftUI/Views/Cards/CardView.swift | 24 +++++++++++++---- .../CollectionStoriesView.swift | 1 + .../SharedWithYouDetailView.swift | 1 + .../Views/Detail views/SlateDetailView.swift | 1 + .../Top level views/RecentSavesView.swift | 1 + .../Top level views/RecommendationsView.swift | 1 + .../Top level views/SharedWithYouView.swift | 1 + 14 files changed, 87 insertions(+), 10 deletions(-) create mode 100644 PocketKit/Sources/PocketKit/Home/SwiftUI/Models/CardType.swift diff --git a/PocketKit/Sources/Analytics/AppEvents/Collection.swift b/PocketKit/Sources/Analytics/AppEvents/Collection.swift index d9decbb8b..efe8e5699 100644 --- a/PocketKit/Sources/Analytics/AppEvents/Collection.swift +++ b/PocketKit/Sources/Analytics/AppEvents/Collection.swift @@ -169,6 +169,21 @@ public extension Events.Collection { } // MARK: Tracking Story + // story card viewed + static func storyImpression(url: String, positionInList: Int?) -> Impression { + return Impression( + component: .card, + requirement: .viewable, + uiEntity: UiEntity( + .card, + identifier: "collection.story.impression", + index: positionInList + ), + extraEntities: [ + ContentEntity(url: url) + ] + ) + } /** * Fired when a user clicks a card on a collection */ diff --git a/PocketKit/Sources/Analytics/AppEvents/ExpandedSlate.swift b/PocketKit/Sources/Analytics/AppEvents/ExpandedSlate.swift index dc32a4057..63fe51162 100644 --- a/PocketKit/Sources/Analytics/AppEvents/ExpandedSlate.swift +++ b/PocketKit/Sources/Analytics/AppEvents/ExpandedSlate.swift @@ -50,7 +50,7 @@ public extension Events.ExpandedSlate { /** Fired when a user sees a card on Home using the /discover API */ - static func SlateArticleImpression(url: String, positionInList: Int, recommendationId: String) -> Impression { + static func SlateArticleImpression(url: String, positionInList: Int?, recommendationId: String) -> Impression { return Impression( component: .card, requirement: .viewable, diff --git a/PocketKit/Sources/Analytics/AppEvents/Home.swift b/PocketKit/Sources/Analytics/AppEvents/Home.swift index 9b716efcb..b0eadc8de 100644 --- a/PocketKit/Sources/Analytics/AppEvents/Home.swift +++ b/PocketKit/Sources/Analytics/AppEvents/Home.swift @@ -11,7 +11,7 @@ public extension Events.Home { /** Fired when a card in the `Recent Saves` section scrolls into view */ - static func RecentSavesCardImpression(url: String, positionInList: Int) -> Impression { + static func RecentSavesCardImpression(url: String, positionInList: Int?) -> Impression { return Impression( component: .card, requirement: .viewable, @@ -112,7 +112,7 @@ public extension Events.Home { /** Fired when a user sees a card on Home using the homeSlateLineup API */ - static func SlateArticleImpression(url: String, positionInList: Int, recommendationId: String) -> Impression { + static func SlateArticleImpression(url: String, positionInList: Int?, recommendationId: String) -> Impression { return Impression( component: .card, requirement: .viewable, @@ -204,7 +204,7 @@ public extension Events.Home { // MARK: Shared With You /// Shared With You card viewed - static func sharedWithYouCardImpression(url: String, positionInList: Int) -> Impression { + static func sharedWithYouCardImpression(url: String, positionInList: Int?) -> Impression { return Impression( component: .card, requirement: .viewable, diff --git a/PocketKit/Sources/Analytics/AppEvents/SharedWithYou.swift b/PocketKit/Sources/Analytics/AppEvents/SharedWithYou.swift index ffe33de8d..4bdc6bd58 100644 --- a/PocketKit/Sources/Analytics/AppEvents/SharedWithYou.swift +++ b/PocketKit/Sources/Analytics/AppEvents/SharedWithYou.swift @@ -22,7 +22,7 @@ public extension Events.SharedWithYou { } /// Shared With You card viewed - static func cardImpression(url: String, index: Int) -> Impression { + static func cardImpression(url: String, index: Int?) -> Impression { return Impression( component: .card, requirement: .viewable, diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/CardType.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/CardType.swift new file mode 100644 index 000000000..fc8e6f35d --- /dev/null +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/CardType.swift @@ -0,0 +1,13 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// Identifies the type of a card for analytics purposes +enum CardType { + case recentSave + case recommendation + case sharedWithYou + case collectionStory + case slateDetail + case sharedWithYouDetail +} diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift index b11a6cb5a..402a5e3a3 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift @@ -2,6 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +import Analytics @preconcurrency import Sync // TODO: SWIFTUI - Add analytics @@ -57,3 +58,28 @@ struct HomeActions { try? await source.fetchCollection(by: slug) } } + +// MARK: Analytics +extension HomeActions { + func trackCardImpression(_ type: CardType, url: String, index: Int? = nil, recommendationID: String? = nil) { + Task { + let tracker = await Services.shared.tracker + switch type { + case .recentSave: + tracker.track(event: Events.Home.RecentSavesCardImpression(url: url, positionInList: index)) + case .recommendation: + guard let recommendationID else { return } + tracker.track(event: Events.Home.SlateArticleImpression(url: url, positionInList: index, recommendationId: recommendationID)) + case .sharedWithYou: + tracker.track(event: Events.Home.sharedWithYouCardImpression(url: url, positionInList: index)) + case .collectionStory: + tracker.track(event: Events.Collection.storyImpression(url: url, positionInList: index)) + case .slateDetail: + guard let recommendationID else { return } + tracker.track(event: Events.ExpandedSlate.SlateArticleImpression(url: url, positionInList: index, recommendationId: recommendationID)) + case .sharedWithYouDetail: + tracker.track(event: Events.SharedWithYou.cardImpression(url: url, index: index)) + } + } + } +} diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeCardConfiguration.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeCardConfiguration.swift index 716afe684..b32d96ba0 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeCardConfiguration.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeCardConfiguration.swift @@ -19,6 +19,7 @@ struct HomeCardConfiguration: Identifiable, @preconcurrency Equatable, Hashable let givenURL: String let sharedWithYouUrlString: String? let showExcerpt: Bool + let type: CardType // actions configuration let enableSaveAction: Bool @@ -33,6 +34,7 @@ struct HomeCardConfiguration: Identifiable, @preconcurrency Equatable, Hashable givenURL: String, sharedWithYouUrlString: String? = nil, showExcerpt: Bool = false, + type: CardType, enableSaveAction: Bool = false, enableFavoriteAction: Bool = false, enableShareMenuAction: Bool = false, @@ -43,6 +45,7 @@ struct HomeCardConfiguration: Identifiable, @preconcurrency Equatable, Hashable self.givenURL = givenURL self.sharedWithYouUrlString = sharedWithYouUrlString self.showExcerpt = showExcerpt + self.type = type self.enableSaveAction = enableSaveAction self.enableFavoriteAction = enableFavoriteAction self.enableShareMenuAction = enableShareMenuAction diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Cards/CardView.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Cards/CardView.swift index 4be6826e4..85d292502 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Cards/CardView.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Cards/CardView.swift @@ -24,8 +24,10 @@ struct CardView: View { @Environment(\.carouselWidth) private var carouselWidth + @Environment(\.homeActions) + private var homeActions - @EnvironmentObject var navigation: HomeNavigation + @EnvironmentObject private var navigation: HomeNavigation @State private var presentWebView: Bool = false @@ -54,16 +56,28 @@ struct CardView: View { } var body: some View { + makeBody() + .onAppear { + homeActions + .trackCardImpression( + card.type, + url: card.givenURL, + recommendationID: item?.recommendation?.analyticsID + ) + } + } +} + +// MARK: View builders +private extension CardView { + @ViewBuilder + func makeBody() -> some View { if let url = card.sharedWithYouUrlString { makeSharedWithYouCard(url) } else { makeSizedCard() } } -} - -// MARK: View builders -private extension CardView { /// Builds the card of the current size /// - Returns: the card view @ViewBuilder diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/Native Collections/CollectionStoriesView.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/Native Collections/CollectionStoriesView.swift index 96e9faf88..10b458103 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/Native Collections/CollectionStoriesView.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/Native Collections/CollectionStoriesView.swift @@ -55,6 +55,7 @@ private extension CollectionStoriesView { givenURL: item.givenURL, sharedWithYouUrlString: nil, showExcerpt: true, + type: .collectionStory, enableSaveAction: true, enableShareMenuAction: true, enableReportMenuAction: true diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/SharedWithYouDetailView.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/SharedWithYouDetailView.swift index 368bb9b77..9d4cd38f4 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/SharedWithYouDetailView.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/SharedWithYouDetailView.swift @@ -43,6 +43,7 @@ private extension SharedWithYouDetailView { return HomeCardConfiguration( givenURL: item.givenURL, sharedWithYouUrlString: $0.url, + type: .sharedWithYouDetail, enableSaveAction: true, enableShareMenuAction: true, enableReportMenuAction: true diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/SlateDetailView.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/SlateDetailView.swift index 22d9db432..b1842973f 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/SlateDetailView.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/SlateDetailView.swift @@ -50,6 +50,7 @@ private extension SlateDetailView { return HomeCardConfiguration( givenURL: item.givenURL, sharedWithYouUrlString: nil, + type: .slateDetail, enableSaveAction: true, enableShareMenuAction: true, enableReportMenuAction: true diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/RecentSavesView.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/RecentSavesView.swift index 488afbf48..8519a9184 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/RecentSavesView.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/RecentSavesView.swift @@ -69,6 +69,7 @@ private extension RecentSavesView { HomeCardConfiguration( givenURL: $0.item?.givenURL ?? $0.url, sharedWithYouUrlString: nil, + type: .recentSave, enableFavoriteAction: true, enableShareMenuAction: true, enableArchiveMenuAction: true, diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/RecommendationsView.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/RecommendationsView.swift index 5cf209262..0b02ff169 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/RecommendationsView.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/RecommendationsView.swift @@ -40,6 +40,7 @@ private extension RecommendationsView { return HomeCardConfiguration( givenURL: item.givenURL, sharedWithYouUrlString: nil, + type: .recommendation, enableSaveAction: true, enableShareMenuAction: true, enableReportMenuAction: true diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/SharedWithYouView.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/SharedWithYouView.swift index ee095f658..c34227cb7 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/SharedWithYouView.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/SharedWithYouView.swift @@ -68,6 +68,7 @@ private extension SharedWithYouView { HomeCardConfiguration( givenURL: $0.item?.givenURL ?? $0.url, sharedWithYouUrlString: $0.url, + type: .sharedWithYou, enableSaveAction: true, enableShareMenuAction: true ) From 044bbb26cdcfe10961819b467cf4dd8f1b16d9a0 Mon Sep 17 00:00:00 2001 From: Gio Date: Thu, 31 Oct 2024 01:06:24 -0500 Subject: [PATCH 2/5] feat(analytics): add card contentOpen events in SwiftUI Home --- .../Analytics/AppEvents/ExpandedSlate.swift | 2 +- .../Analytics/AppEvents/SharedWithYou.swift | 2 +- .../Home/SwiftUI/Models/HomeActions.swift | 24 ++++++++++++++++++- .../Home/SwiftUI/Views/Cards/CardView.swift | 9 +++++++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/PocketKit/Sources/Analytics/AppEvents/ExpandedSlate.swift b/PocketKit/Sources/Analytics/AppEvents/ExpandedSlate.swift index 63fe51162..25c598b6e 100644 --- a/PocketKit/Sources/Analytics/AppEvents/ExpandedSlate.swift +++ b/PocketKit/Sources/Analytics/AppEvents/ExpandedSlate.swift @@ -30,7 +30,7 @@ public extension Events.ExpandedSlate { /** Fired when a user clicks a card on Home using the /discover API */ - static func SlateArticleContentOpen(url: String, positionInList: Int, recommendationId: String, destination: ContentOpen.Destination) -> ContentOpen { + static func SlateArticleContentOpen(url: String, positionInList: Int?, recommendationId: String, destination: ContentOpen.Destination) -> ContentOpen { return ContentOpen( destination: destination, contentEntity: diff --git a/PocketKit/Sources/Analytics/AppEvents/SharedWithYou.swift b/PocketKit/Sources/Analytics/AppEvents/SharedWithYou.swift index 4bdc6bd58..41066e031 100644 --- a/PocketKit/Sources/Analytics/AppEvents/SharedWithYou.swift +++ b/PocketKit/Sources/Analytics/AppEvents/SharedWithYou.swift @@ -38,7 +38,7 @@ public extension Events.SharedWithYou { } /// Shared With You card tapped - static func contentOpen(url: String, index: Int, destination: ContentOpen.Destination) -> ContentOpen { + static func contentOpen(url: String, index: Int?, destination: ContentOpen.Destination) -> ContentOpen { return ContentOpen( destination: destination, trigger: .click, diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift index 402a5e3a3..f3cc90c19 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift @@ -62,7 +62,7 @@ struct HomeActions { // MARK: Analytics extension HomeActions { func trackCardImpression(_ type: CardType, url: String, index: Int? = nil, recommendationID: String? = nil) { - Task { + Task(priority: .background) { let tracker = await Services.shared.tracker switch type { case .recentSave: @@ -82,4 +82,26 @@ extension HomeActions { } } } + + func trackCardContentOpen(_ type: CardType, url: String, index: Int? = nil, recommendationID: String? = nil, externalDestination: Bool) { + Task(priority: .background) { + let tracker = await Services.shared.tracker + switch type { + case .recentSave: + tracker.track(event: Events.Home.RecentSavesCardContentOpen(url: url, positionInList: index)) + case .recommendation: + guard let recommendationID else { return } + tracker.track(event: Events.Home.SlateArticleContentOpen(url: url, positionInList: index, recommendationId: recommendationID, destination: externalDestination ? .external : .internal)) + case .sharedWithYou: + tracker.track(event: Events.Home.sharedWithYouContentOpen(url: url, positionInList: index, destination: externalDestination ? .external : .internal)) + case .collectionStory: + tracker.track(event: Events.Collection.contentOpen(url: url)) + case .slateDetail: + guard let recommendationID else { return } + tracker.track(event: Events.ExpandedSlate.SlateArticleContentOpen(url: url, positionInList: index, recommendationId: recommendationID, destination: externalDestination ? .external : .internal)) + case .sharedWithYouDetail: + tracker.track(event: Events.SharedWithYou.contentOpen(url: url, index: index, destination: externalDestination ? .external : .internal)) + } + } + } } diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Cards/CardView.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Cards/CardView.swift index 85d292502..d7ec1c5bf 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Cards/CardView.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Cards/CardView.swift @@ -122,6 +122,8 @@ private extension CardView { .ignoresSafeArea(.all) } .onTapGesture { + // property that determines if the content is opened in Pocket or in a webview, for analytics purposes + var externalDestination = false if let slug = item?.collection?.slug { navigation.navigateTo(NativeCollectionDestination(slug: slug, givenURL: card.givenURL)) } else if savedItem != nil { @@ -129,8 +131,15 @@ private extension CardView { } else if item?.syndicatedArticle != nil { navigation.navigateTo(ReadableDestination(.syndicated(card.givenURL))) } else if URL(string: card.givenURL) != nil { + externalDestination = true presentWebView = true } + homeActions.trackCardContentOpen( + card.type, + url: card.givenURL, + recommendationID: item?.recommendation?.analyticsID, + externalDestination: externalDestination + ) } } From 344ac946142523cb1334005b0498a99c38dd3041 Mon Sep 17 00:00:00 2001 From: Gio Date: Thu, 31 Oct 2024 21:07:16 -0500 Subject: [PATCH 3/5] feat(analytics): add save, archive, favorite, unfavorite, delete and share analytics to SwiftUI Home --- .../Analytics/AppEvents/Collection.swift | 2 +- .../Analytics/AppEvents/ExpandedSlate.swift | 12 +- .../Sources/Analytics/AppEvents/Home.swift | 52 +++-- .../Analytics/AppEvents/SharedWithYou.swift | 4 +- .../PocketKit/Home/HomeViewModel.swift | 20 +- .../Home/Lists/SlateDetailViewModel.swift | 10 +- .../Home/SwiftUI/Models/AnalyticsInfo.swift | 20 ++ .../Home/SwiftUI/Models/CardType.swift | 1 + .../Home/SwiftUI/Models/HomeActions.swift | 177 +++++++++++++++--- .../Models/HomeCardConfiguration.swift | 3 + .../Cards/Card elements/CardFooter.swift | 58 +++++- .../Home/SwiftUI/Views/Cards/CardView.swift | 20 +- .../CollectionStoriesView.swift | 5 +- .../NativeCollectionView.swift | 20 +- .../SharedWithYouDetailView.swift | 7 +- .../Views/Detail views/SlateDetailView.swift | 5 +- .../Top level views/RecentSavesView.swift | 5 +- .../Top level views/RecommendationsView.swift | 1 + .../Top level views/SharedWithYouView.swift | 7 +- .../Report/ReportRecommendationView.swift | 2 +- 20 files changed, 337 insertions(+), 94 deletions(-) create mode 100644 PocketKit/Sources/PocketKit/Home/SwiftUI/Models/AnalyticsInfo.swift diff --git a/PocketKit/Sources/Analytics/AppEvents/Collection.swift b/PocketKit/Sources/Analytics/AppEvents/Collection.swift index efe8e5699..c595bc055 100644 --- a/PocketKit/Sources/Analytics/AppEvents/Collection.swift +++ b/PocketKit/Sources/Analytics/AppEvents/Collection.swift @@ -170,7 +170,7 @@ public extension Events.Collection { // MARK: Tracking Story // story card viewed - static func storyImpression(url: String, positionInList: Int?) -> Impression { + static func storyImpression(url: String, positionInList: Int) -> Impression { return Impression( component: .card, requirement: .viewable, diff --git a/PocketKit/Sources/Analytics/AppEvents/ExpandedSlate.swift b/PocketKit/Sources/Analytics/AppEvents/ExpandedSlate.swift index 25c598b6e..58ca8d745 100644 --- a/PocketKit/Sources/Analytics/AppEvents/ExpandedSlate.swift +++ b/PocketKit/Sources/Analytics/AppEvents/ExpandedSlate.swift @@ -11,7 +11,7 @@ public extension Events.ExpandedSlate { /** Fired when a user views a slate in detail */ - static func SlateExpanded(slateId: String, slateRequestId: String, slateExperimentId: String, slateIndex: Int, slateLineupId: String, slateLineupRequestId: String, slateLineupExperimentId: String) -> Impression { + static func slateExpanded(slateId: String, slateRequestId: String, slateExperimentId: String, slateIndex: Int, slateLineupId: String, slateLineupRequestId: String, slateLineupExperimentId: String) -> Impression { return Impression( component: .screen, requirement: .viewable, @@ -30,7 +30,7 @@ public extension Events.ExpandedSlate { /** Fired when a user clicks a card on Home using the /discover API */ - static func SlateArticleContentOpen(url: String, positionInList: Int?, recommendationId: String, destination: ContentOpen.Destination) -> ContentOpen { + static func slateArticleContentOpen(url: String, positionInList: Int, recommendationId: String, destination: ContentOpen.Destination) -> ContentOpen { return ContentOpen( destination: destination, contentEntity: @@ -50,7 +50,7 @@ public extension Events.ExpandedSlate { /** Fired when a user sees a card on Home using the /discover API */ - static func SlateArticleImpression(url: String, positionInList: Int?, recommendationId: String) -> Impression { + static func slateArticleImpression(url: String, positionInList: Int, recommendationId: String) -> Impression { return Impression( component: .card, requirement: .viewable, @@ -69,7 +69,7 @@ public extension Events.ExpandedSlate { /** Fired when a user saves a card on Home using the /discover API */ - static func SlateArticleSave(url: String, positionInList: Int, recommendationId: String) -> Engagement { + static func slateArticleSave(url: String, positionInList: Int?, recommendationId: String) -> Engagement { return Engagement( .save( contentEntity: ContentEntity(url: url) @@ -89,7 +89,7 @@ public extension Events.ExpandedSlate { /** Fired when a user archives a card on Home using the /discover API */ - static func SlateArticleArchive(url: String, positionInList: Int, recommendationId: String) -> Engagement { + static func slateArticleArchive(url: String, positionInList: Int, recommendationId: String) -> Engagement { return Engagement( .general, uiEntity: UiEntity( @@ -107,7 +107,7 @@ public extension Events.ExpandedSlate { /** Fired when a user shares a card on Home using the /discover API */ - static func SlateArticleShare(url: String, positionInList: Int, recommendationId: String) -> Engagement { + static func slateArticleShare(url: String, positionInList: Int, recommendationId: String) -> Engagement { return Engagement( .general, uiEntity: UiEntity( diff --git a/PocketKit/Sources/Analytics/AppEvents/Home.swift b/PocketKit/Sources/Analytics/AppEvents/Home.swift index b0eadc8de..d879bc23e 100644 --- a/PocketKit/Sources/Analytics/AppEvents/Home.swift +++ b/PocketKit/Sources/Analytics/AppEvents/Home.swift @@ -11,7 +11,7 @@ public extension Events.Home { /** Fired when a card in the `Recent Saves` section scrolls into view */ - static func RecentSavesCardImpression(url: String, positionInList: Int?) -> Impression { + static func recentSavesCardImpression(url: String, positionInList: Int) -> Impression { return Impression( component: .card, requirement: .viewable, @@ -29,7 +29,7 @@ public extension Events.Home { /** Fired when a card in the `Recent Saves` section is shared */ - static func RecentSavesCardShare(url: String, positionInList: Int) -> Engagement { + static func recentSavesCardShare(url: String, positionInList: Int) -> Engagement { return Engagement( uiEntity: UiEntity( .button, @@ -45,7 +45,7 @@ public extension Events.Home { /** Fired when a card in the `Recent Saves` section is deleted */ - static func RecentSavesCardDelete(url: String, positionInList: Int) -> Engagement { + static func recentSavesCardDelete(url: String, positionInList: Int) -> Engagement { return Engagement( uiEntity: UiEntity( .button, @@ -61,7 +61,7 @@ public extension Events.Home { /** Fired when a card in the `Recent Saves` section is archived */ - static func RecentSavesCardArchive(url: String, positionInList: Int) -> Engagement { + static func recentSavesCardArchive(url: String, positionInList: Int) -> Engagement { return Engagement( uiEntity: UiEntity( .button, @@ -74,10 +74,36 @@ public extension Events.Home { ) } + static func recentSavesCardFavorite(url: String, positionInList: Int) -> Engagement { + return Engagement( + uiEntity: UiEntity( + .button, + identifier: "home.recent.favorite", + index: positionInList + ), + extraEntities: [ + ContentEntity(url: url) + ] + ) + } + + static func recentSavesCardUnfavorite(url: String, positionInList: Int) -> Engagement { + return Engagement( + uiEntity: UiEntity( + .button, + identifier: "home.recent.unfavorite", + index: positionInList + ), + extraEntities: [ + ContentEntity(url: url) + ] + ) + } + /** Fired when a user clicks a card in the `Recent Saves` section */ - static func RecentSavesCardContentOpen(url: String, positionInList: Int?) -> ContentOpen { + static func recentSavesCardContentOpen(url: String, positionInList: Int?) -> ContentOpen { return ContentOpen( contentEntity: ContentEntity(url: url), @@ -92,7 +118,7 @@ public extension Events.Home { /** Fired when a user clicks a card on Home using the homeSlateLineup API */ - static func SlateArticleContentOpen(url: String, positionInList: Int?, recommendationId: String, destination: ContentOpen.Destination) -> ContentOpen { + static func slateArticleContentOpen(url: String, positionInList: Int?, recommendationId: String, destination: ContentOpen.Destination) -> ContentOpen { return ContentOpen( destination: destination, contentEntity: @@ -112,7 +138,7 @@ public extension Events.Home { /** Fired when a user sees a card on Home using the homeSlateLineup API */ - static func SlateArticleImpression(url: String, positionInList: Int?, recommendationId: String) -> Impression { + static func slateArticleImpression(url: String, positionInList: Int, recommendationId: String) -> Impression { return Impression( component: .card, requirement: .viewable, @@ -131,7 +157,7 @@ public extension Events.Home { /** Fired when a user saves a card on Home using the homeSlateLineup API */ - static func SlateArticleSave(url: String, positionInList: Int, recommendationId: String) -> Engagement { + static func slateArticleSave(url: String, positionInList: Int, recommendationId: String) -> Engagement { return Engagement( .save( contentEntity: ContentEntity(url: url) @@ -150,7 +176,7 @@ public extension Events.Home { /** Fired when a user archives a card on Home using the /discover API */ - static func SlateArticleArchive(url: String, positionInList: Int, recommendationId: String) -> Engagement { + static func slateArticleArchive(url: String, positionInList: Int, recommendationId: String) -> Engagement { return Engagement( .general, uiEntity: UiEntity( @@ -168,7 +194,7 @@ public extension Events.Home { /** Fired when a user shares a card on Home using the /discover API */ - static func SlateArticleShare(url: String, positionInList: Int, recommendationId: String) -> Engagement { + static func slateArticleShare(url: String, positionInList: Int, recommendationId: String) -> Engagement { return Engagement( .general, uiEntity: UiEntity( @@ -186,7 +212,7 @@ public extension Events.Home { /** Fired when a user selects the report action on Home using the /discover API */ - static func SlateArticleReport(url: String, reason: ReportEntity.Reason, recommendationId: String, comment: String?) -> Engagement { + static func slateArticleReport(url: String, reason: ReportEntity.Reason, recommendationId: String, comment: String?) -> Engagement { return Engagement( .report( reportEntity: ReportEntity(reason: reason, comment: comment), @@ -233,7 +259,7 @@ public extension Events.Home { } /// Shared With You Item saved - static func sharedWithYouItemSave(url: String, positionInList: Int) -> Engagement { + static func sharedWithYouItemSave(url: String, positionInList: Int?) -> Engagement { return Engagement( uiEntity: UiEntity( .button, @@ -247,7 +273,7 @@ public extension Events.Home { } /// Shared With You item unsaved - static func sharedWithYouItemArchive(url: String, positionInList: Int) -> Engagement { + static func sharedWithYouItemArchive(url: String, positionInList: Int?) -> Engagement { return Engagement( uiEntity: UiEntity( .button, diff --git a/PocketKit/Sources/Analytics/AppEvents/SharedWithYou.swift b/PocketKit/Sources/Analytics/AppEvents/SharedWithYou.swift index 41066e031..ffe33de8d 100644 --- a/PocketKit/Sources/Analytics/AppEvents/SharedWithYou.swift +++ b/PocketKit/Sources/Analytics/AppEvents/SharedWithYou.swift @@ -22,7 +22,7 @@ public extension Events.SharedWithYou { } /// Shared With You card viewed - static func cardImpression(url: String, index: Int?) -> Impression { + static func cardImpression(url: String, index: Int) -> Impression { return Impression( component: .card, requirement: .viewable, @@ -38,7 +38,7 @@ public extension Events.SharedWithYou { } /// Shared With You card tapped - static func contentOpen(url: String, index: Int?, destination: ContentOpen.Destination) -> ContentOpen { + static func contentOpen(url: String, index: Int, destination: ContentOpen.Destination) -> ContentOpen { return ContentOpen( destination: destination, trigger: .click, diff --git a/PocketKit/Sources/PocketKit/Home/HomeViewModel.swift b/PocketKit/Sources/PocketKit/Home/HomeViewModel.swift index f35c1b8fb..dc2fbc668 100644 --- a/PocketKit/Sources/PocketKit/Home/HomeViewModel.swift +++ b/PocketKit/Sources/PocketKit/Home/HomeViewModel.swift @@ -498,7 +498,7 @@ extension HomeViewModel { ) { switch source { case .app: - tracker.track(event: Events.Home.SlateArticleContentOpen( + tracker.track(event: Events.Home.slateArticleContentOpen( url: url, positionInList: positionInList, recommendationId: recommendationId, @@ -598,7 +598,7 @@ extension HomeViewModel { private func trackRecentSavesOpen(url: String, positionInList: Int?, source: ReadableSource) { switch source { case .app: - tracker.track(event: Events.Home.RecentSavesCardContentOpen(url: url, positionInList: positionInList)) + tracker.track(event: Events.Home.recentSavesCardContentOpen(url: url, positionInList: positionInList)) case .external: tracker.track(event: Events.Deeplinks.deeplinkArticleContentOpen(url: url, destination: .internal)) case .widget: @@ -731,7 +731,7 @@ extension HomeViewModel { private func delete(item: CDSavedItem, indexPath: IndexPath) { presentedAlert = nil - tracker.track(event: Events.Home.RecentSavesCardDelete(url: item.url, positionInList: indexPath.item)) + tracker.track(event: Events.Home.recentSavesCardDelete(url: item.url, positionInList: indexPath.item)) source.delete(item: item) } } @@ -856,7 +856,7 @@ extension HomeViewModel { // This view model is used within the context of a view that is presented within Saves let shareableUrl = await shareableUrl(recommendation.item) ?? recommendation.item.bestURL self.sharedActivity = PocketItemActivity.fromHome(url: shareableUrl, sender: sender) - tracker.track(event: Events.Home.SlateArticleShare(url: shareableUrl, positionInList: indexPath.item, recommendationId: recommendation.analyticsID)) + tracker.track(event: Events.Home.slateArticleShare(url: shareableUrl, positionInList: indexPath.item, recommendationId: recommendation.analyticsID)) } private func share(_ savedItem: CDSavedItem, at indexPath: IndexPath, with sender: Any?) async { @@ -864,7 +864,7 @@ extension HomeViewModel { // within the context of "Recent Saves" let shareableUrl = await shareableUrl(savedItem.item) ?? savedItem.url self.sharedActivity = PocketItemActivity.fromSaves(url: shareableUrl, sender: sender) - tracker.track(event: Events.Home.RecentSavesCardShare(url: shareableUrl, positionInList: indexPath.item)) + tracker.track(event: Events.Home.recentSavesCardShare(url: shareableUrl, positionInList: indexPath.item)) } private func share(_ sharedWithYouItem: CDSharedWithYouItem, at indexPath: IndexPath, with sender: Any?) async { @@ -889,18 +889,18 @@ extension HomeViewModel { private func save(_ recommendation: CDRecommendation, at indexPath: IndexPath) { source.save(recommendation: recommendation) let givenURL = recommendation.item.givenURL - tracker.track(event: Events.Home.SlateArticleSave(url: givenURL, positionInList: indexPath.item, recommendationId: recommendation.analyticsID)) + tracker.track(event: Events.Home.slateArticleSave(url: givenURL, positionInList: indexPath.item, recommendationId: recommendation.analyticsID)) } private func archive(_ recommendation: CDRecommendation, at indexPath: IndexPath) { source.archive(recommendation: recommendation) let givenURL = recommendation.item.givenURL - tracker.track(event: Events.Home.SlateArticleArchive(url: givenURL, positionInList: indexPath.item, recommendationId: recommendation.analyticsID)) + tracker.track(event: Events.Home.slateArticleArchive(url: givenURL, positionInList: indexPath.item, recommendationId: recommendation.analyticsID)) } private func archive(_ savedItem: CDSavedItem, at indexPath: IndexPath) { self.source.archive(item: savedItem) - tracker.track(event: Events.Home.RecentSavesCardArchive(url: savedItem.url, positionInList: indexPath.item)) + tracker.track(event: Events.Home.recentSavesCardArchive(url: savedItem.url, positionInList: indexPath.item)) } } @@ -919,7 +919,7 @@ extension HomeViewModel { guard let savedItem = source.viewObject(id: objectID) as? CDSavedItem else { return } - tracker.track(event: Events.Home.RecentSavesCardImpression(url: savedItem.url, positionInList: indexPath.item)) + tracker.track(event: Events.Home.recentSavesCardImpression(url: savedItem.url, positionInList: indexPath.item)) return case .recommendationHero(let objectID), .recommendationCarousel(let objectID): guard let recommendation = source.viewObject(id: objectID) as? CDRecommendation else { @@ -931,7 +931,7 @@ extension HomeViewModel { } let givenURL = item.givenURL - tracker.track(event: Events.Home.SlateArticleImpression(url: givenURL, positionInList: indexPath.item, recommendationId: recommendation.analyticsID)) + tracker.track(event: Events.Home.slateArticleImpression(url: givenURL, positionInList: indexPath.item, recommendationId: recommendation.analyticsID)) case .singinBanner: tracker.track(event: Events.SignedOut.signinBannerImpression()) return diff --git a/PocketKit/Sources/PocketKit/Home/Lists/SlateDetailViewModel.swift b/PocketKit/Sources/PocketKit/Home/Lists/SlateDetailViewModel.swift index 07fec4a79..1a7616548 100644 --- a/PocketKit/Sources/PocketKit/Home/Lists/SlateDetailViewModel.swift +++ b/PocketKit/Sources/PocketKit/Home/Lists/SlateDetailViewModel.swift @@ -88,7 +88,7 @@ class SlateDetailViewModel { Log.capture(message: "Tried to display slate without slatelineup, not logging analytics") return } - tracker.track(event: Events.ExpandedSlate.SlateExpanded(slateId: slate.remoteID, slateRequestId: slate.requestID, slateExperimentId: slate.experimentID, slateIndex: slateIndex, slateLineupId: slateLineup.remoteID, slateLineupRequestId: slateLineup.requestID, slateLineupExperimentId: slateLineup.experimentID)) + tracker.track(event: Events.ExpandedSlate.slateExpanded(slateId: slate.remoteID, slateRequestId: slate.requestID, slateExperimentId: slate.experimentID, slateIndex: slateIndex, slateLineupId: slateLineup.remoteID, slateLineupRequestId: slateLineup.requestID, slateLineupExperimentId: slateLineup.experimentID)) } func fetch() { @@ -116,7 +116,7 @@ class SlateDetailViewModel { } let givenURL = item.givenURL - tracker.track(event: Events.ExpandedSlate.SlateArticleImpression(url: givenURL, positionInList: indexPath.item, recommendationId: recommendation.analyticsID)) + tracker.track(event: Events.ExpandedSlate.slateArticleImpression(url: givenURL, positionInList: indexPath.item, recommendationId: recommendation.analyticsID)) } } } @@ -177,7 +177,7 @@ extension SlateDetailViewModel { } let givenURL = item.givenURL - tracker.track(event: Events.ExpandedSlate.SlateArticleContentOpen(url: givenURL, positionInList: indexPath.item, recommendationId: recommendation.analyticsID, destination: destination)) + tracker.track(event: Events.ExpandedSlate.slateArticleContentOpen(url: givenURL, positionInList: indexPath.item, recommendationId: recommendation.analyticsID, destination: destination)) } } @@ -232,13 +232,13 @@ extension SlateDetailViewModel { private func save(_ recommendation: CDRecommendation, at indexPath: IndexPath) { source.save(recommendation: recommendation) let givenURL = recommendation.item.givenURL - tracker.track(event: Events.ExpandedSlate.SlateArticleSave(url: givenURL, positionInList: indexPath.item, recommendationId: recommendation.analyticsID)) + tracker.track(event: Events.ExpandedSlate.slateArticleSave(url: givenURL, positionInList: indexPath.item, recommendationId: recommendation.analyticsID)) } private func archive(_ recommendation: CDRecommendation, at indexPath: IndexPath) { source.archive(recommendation: recommendation) let givenURL = recommendation.item.givenURL - tracker.track(event: Events.ExpandedSlate.SlateArticleArchive(url: givenURL, positionInList: indexPath.item, recommendationId: recommendation.analyticsID)) + tracker.track(event: Events.ExpandedSlate.slateArticleArchive(url: givenURL, positionInList: indexPath.item, recommendationId: recommendation.analyticsID)) } private func report(_ recommendation: CDRecommendation, at indexPath: IndexPath) { diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/AnalyticsInfo.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/AnalyticsInfo.swift new file mode 100644 index 000000000..8d3d08db2 --- /dev/null +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/AnalyticsInfo.swift @@ -0,0 +1,20 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// Set of info to send along actions for analytics purposes +struct AnalyticsInfo { + let type: CardType + let url: String + let index: Int + let recommendationID: String? + let externalDestination: Bool + + init(type: CardType, url: String, index: Int = 0, recommendationID: String? = nil, externalDestination: Bool = false) { + self.type = type + self.url = url + self.index = index + self.recommendationID = recommendationID + self.externalDestination = externalDestination + } +} diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/CardType.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/CardType.swift index fc8e6f35d..b7f46f00f 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/CardType.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/CardType.swift @@ -7,6 +7,7 @@ enum CardType { case recentSave case recommendation case sharedWithYou + case collection // This is not a card per se, but contains the same operations as a card case collectionStory case slateDetail case sharedWithYouDetail diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift index f3cc90c19..9d1ae98be 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift @@ -8,39 +8,44 @@ import Analytics // TODO: SWIFTUI - Add analytics /// Type that contains all the actions that can be performed from Home and its detail views struct HomeActions { - // TODO: SWIFTUI - the following methods use a reference to Services that only lives in their scope. - // This is on purpose since we do not want to keep a reference in the model, and once we are fully - // migrated to SwiftUI we will likely leverage the environment for dependency injection. + // TODO: SWIFTUI - the methods here use a reference to Services that only lives in their scope. + // This is on purpose since we do not want to keep a reference here, and once we are fully + // migrated to SwiftUI we will likely leverage the environment or something like swift-dependencies + // /~https://github.com/pointfreeco/swift-dependencies for dependency injection. @MainActor - func saveAction(isSaved: Bool, givenURL: String) { + func saveAction(isSaved: Bool, givenURL: String, info: AnalyticsInfo) { let source = Services.shared.source if isSaved { source.archive(from: givenURL) } else { source.save(from: givenURL) } + trackSave(info) } @MainActor - func archiveAction(givenURL: String) { + func archiveAction(givenURL: String, info: AnalyticsInfo) { let source = Services.shared.source source.archive(from: givenURL) + trackArchive(info) } @MainActor - func deleteAction(givenURL: String) { + func deleteAction(givenURL: String, info: AnalyticsInfo) { let source = Services.shared.source source.delete(from: givenURL) + trackDelete(info) } @MainActor - func favoriteAction(isFavorite: Bool, givenURL: String) { + func favoriteAction(isFavorite: Bool, givenURL: String, info: AnalyticsInfo) { let source = Services.shared.source if isFavorite { source.unFavorite(givenURL) } else { source.favorite(givenURL) } + trackFavorite(info) } func shareableUrl(shareURL: String?, givenURL: String) async -> String? { @@ -61,46 +66,164 @@ struct HomeActions { // MARK: Analytics extension HomeActions { - func trackCardImpression(_ type: CardType, url: String, index: Int? = nil, recommendationID: String? = nil) { + func trackCardImpression(_ info: AnalyticsInfo) { Task(priority: .background) { let tracker = await Services.shared.tracker - switch type { + switch info.type { case .recentSave: - tracker.track(event: Events.Home.RecentSavesCardImpression(url: url, positionInList: index)) + tracker.track(event: Events.Home.recentSavesCardImpression(url: info.url, positionInList: info.index)) case .recommendation: - guard let recommendationID else { return } - tracker.track(event: Events.Home.SlateArticleImpression(url: url, positionInList: index, recommendationId: recommendationID)) + guard let recommendationID = info.recommendationID else { return } + tracker.track(event: Events.Home.slateArticleImpression(url: info.url, positionInList: info.index, recommendationId: recommendationID)) case .sharedWithYou: - tracker.track(event: Events.Home.sharedWithYouCardImpression(url: url, positionInList: index)) + tracker.track(event: Events.Home.sharedWithYouCardImpression(url: info.url, positionInList: info.index)) case .collectionStory: - tracker.track(event: Events.Collection.storyImpression(url: url, positionInList: index)) + tracker.track(event: Events.Collection.storyImpression(url: info.url, positionInList: info.index)) case .slateDetail: - guard let recommendationID else { return } - tracker.track(event: Events.ExpandedSlate.SlateArticleImpression(url: url, positionInList: index, recommendationId: recommendationID)) + guard let recommendationID = info.recommendationID else { return } + tracker.track(event: Events.ExpandedSlate.slateArticleImpression(url: info.url, positionInList: info.index, recommendationId: recommendationID)) case .sharedWithYouDetail: - tracker.track(event: Events.SharedWithYou.cardImpression(url: url, index: index)) + tracker.track(event: Events.SharedWithYou.cardImpression(url: info.url, index: info.index)) + case .collection: + tracker.track(event: Events.Collection.screenView()) } } } - func trackCardContentOpen(_ type: CardType, url: String, index: Int? = nil, recommendationID: String? = nil, externalDestination: Bool) { + func trackCardContentOpen(_ info: AnalyticsInfo) { Task(priority: .background) { let tracker = await Services.shared.tracker - switch type { + switch info.type { case .recentSave: - tracker.track(event: Events.Home.RecentSavesCardContentOpen(url: url, positionInList: index)) + tracker.track(event: Events.Home.recentSavesCardContentOpen(url: info.url, positionInList: info.index)) case .recommendation: - guard let recommendationID else { return } - tracker.track(event: Events.Home.SlateArticleContentOpen(url: url, positionInList: index, recommendationId: recommendationID, destination: externalDestination ? .external : .internal)) + guard let recommendationID = info.recommendationID else { return } + tracker.track(event: Events.Home.slateArticleContentOpen(url: info.url, positionInList: info.index, recommendationId: recommendationID, destination: info.externalDestination ? .external : .internal)) case .sharedWithYou: - tracker.track(event: Events.Home.sharedWithYouContentOpen(url: url, positionInList: index, destination: externalDestination ? .external : .internal)) + tracker.track(event: Events.Home.sharedWithYouContentOpen(url: info.url, positionInList: info.index, destination: info.externalDestination ? .external : .internal)) case .collectionStory: - tracker.track(event: Events.Collection.contentOpen(url: url)) + tracker.track(event: Events.Collection.contentOpen(url: info.url)) case .slateDetail: - guard let recommendationID else { return } - tracker.track(event: Events.ExpandedSlate.SlateArticleContentOpen(url: url, positionInList: index, recommendationId: recommendationID, destination: externalDestination ? .external : .internal)) + guard let recommendationID = info.recommendationID else { return } + tracker.track(event: Events.ExpandedSlate.slateArticleContentOpen(url: info.url, positionInList: info.index, recommendationId: recommendationID, destination: info.externalDestination ? .external : .internal)) case .sharedWithYouDetail: - tracker.track(event: Events.SharedWithYou.contentOpen(url: url, index: index, destination: externalDestination ? .external : .internal)) + tracker.track(event: Events.SharedWithYou.contentOpen(url: info.url, index: info.index, destination: info.externalDestination ? .external : .internal)) + case .collection: + break // no content open event for collection, the content open is handled by the source card + } + } + } + + func trackSave(_ info: AnalyticsInfo) { + Task(priority: .background) { + let tracker = await Services.shared.tracker + switch info.type { + case .recentSave: + break // by definition, recent saves cannot be saved again + case .recommendation: + guard let recommendationID = info.recommendationID else { return } + tracker.track(event: Events.Home.slateArticleSave(url: info.url, positionInList: info.index, recommendationId: recommendationID)) + case .sharedWithYou: + tracker.track(event: Events.Home.sharedWithYouItemSave(url: info.url, positionInList: info.index)) + case .collectionStory: + tracker.track(event: Events.Collection.saveClicked(url: info.url)) + case .slateDetail: + guard let recommendationID = info.recommendationID else { return } + tracker.track(event: Events.ExpandedSlate.slateArticleSave(url: info.url, positionInList: info.index, recommendationId: recommendationID)) + case .sharedWithYouDetail: + tracker.track(event: Events.SharedWithYou.itemSaved(url: info.url, index: info.index)) + case .collection: + tracker.track(event: Events.Collection.saveClicked(url: info.url)) + } + } + } + + func trackShare(_ info: AnalyticsInfo) { + Task(priority: .background) { + let tracker = await Services.shared.tracker + switch info.type { + case .recentSave: + tracker.track(event: Events.Home.recentSavesCardShare(url: info.url, positionInList: info.index)) + case .recommendation: + guard let recommendationID = info.recommendationID else { return } + tracker.track(event: Events.Home.slateArticleShare(url: info.url, positionInList: info.index, recommendationId: recommendationID)) + case .sharedWithYou: + tracker.track(event: Events.Home.sharedWithYouItemShare(url: info.url, positionInList: info.index)) + case .collectionStory: + tracker.track(event: Events.Collection.shareClicked(url: info.url)) + case .slateDetail: + guard let recommendationID = info.recommendationID else { return } + tracker.track(event: Events.ExpandedSlate.slateArticleShare(url: info.url, positionInList: info.index, recommendationId: recommendationID)) + case .sharedWithYouDetail: + tracker.track(event: Events.SharedWithYou.itemShared(url: info.url, index: info.index)) + case .collection: + tracker.track(event: Events.Collection.shareClicked(url: info.url)) + } + } + } + + func trackArchive(_ info: AnalyticsInfo) { + Task(priority: .background) { + let tracker = await Services.shared.tracker + switch info.type { + case .recentSave: + tracker.track(event: Events.Home.recentSavesCardArchive(url: info.url, positionInList: info.index)) + case .recommendation: + guard let recommendationID = info.recommendationID else { return } + tracker.track(event: Events.Home.slateArticleArchive(url: info.url, positionInList: info.index, recommendationId: recommendationID)) + case .sharedWithYou: + tracker.track(event: Events.Home.sharedWithYouItemArchive(url: info.url, positionInList: info.index)) + case .collectionStory: + tracker.track(event: Events.Collection.unsaveClicked(url: info.url)) + case .slateDetail: + guard let recommendationID = info.recommendationID else { return } + tracker.track(event: Events.ExpandedSlate.slateArticleArchive(url: info.url, positionInList: info.index, recommendationId: recommendationID)) + case .sharedWithYouDetail: + tracker.track(event: Events.SharedWithYou.itemArchived(url: info.url, index: info.index)) + case .collection: + tracker.track(event: Events.Collection.unsaveClicked(url: info.url)) + } + } + } + + func trackFavorite(_ info: AnalyticsInfo) { + Task(priority: .background) { + let tracker = await Services.shared.tracker + switch info.type { + case .recentSave: + tracker.track(event: Events.Home.recentSavesCardFavorite(url: info.url, positionInList: info.index)) + case .collection: + tracker.track(event: Events.Collection.favoriteClicked(url: info.url)) + default: + break // only recent saves can be favorited + } + } + } + + func trackUnFavorite(_ info: AnalyticsInfo) { + Task(priority: .background) { + let tracker = await Services.shared.tracker + switch info.type { + case .recentSave: + tracker.track(event: Events.Home.recentSavesCardUnfavorite(url: info.url, positionInList: info.index)) + case .collection: + tracker.track(event: Events.Collection.unfavoriteClicked(url: info.url)) + default: + break // only recent saves can be favorited. Events from the reader are handled in UIKit for now. + } + } + } + + func trackDelete(_ info: AnalyticsInfo) { + Task(priority: .background) { + let tracker = await Services.shared.tracker + switch info.type { + case .recentSave: + tracker.track(event: Events.Home.recentSavesCardDelete(url: info.url, positionInList: info.index)) + case .collection: + tracker.track(event: Events.Collection.deleteClicked(url: info.url)) + default: + break // only recent saves and collections can be deleted. Events from the reader are handled in UIKit for now. } } } diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeCardConfiguration.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeCardConfiguration.swift index b32d96ba0..0fe921850 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeCardConfiguration.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeCardConfiguration.swift @@ -20,6 +20,7 @@ struct HomeCardConfiguration: Identifiable, @preconcurrency Equatable, Hashable let sharedWithYouUrlString: String? let showExcerpt: Bool let type: CardType + let index: Int // actions configuration let enableSaveAction: Bool @@ -35,6 +36,7 @@ struct HomeCardConfiguration: Identifiable, @preconcurrency Equatable, Hashable sharedWithYouUrlString: String? = nil, showExcerpt: Bool = false, type: CardType, + index: Int, enableSaveAction: Bool = false, enableFavoriteAction: Bool = false, enableShareMenuAction: Bool = false, @@ -46,6 +48,7 @@ struct HomeCardConfiguration: Identifiable, @preconcurrency Equatable, Hashable self.sharedWithYouUrlString = sharedWithYouUrlString self.showExcerpt = showExcerpt self.type = type + self.index = index self.enableSaveAction = enableSaveAction self.enableFavoriteAction = enableFavoriteAction self.enableShareMenuAction = enableShareMenuAction diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Cards/Card elements/CardFooter.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Cards/Card elements/CardFooter.swift index 45c604fe8..baa749ecf 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Cards/Card elements/CardFooter.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Cards/Card elements/CardFooter.swift @@ -52,7 +52,14 @@ private extension CardFooter { Button(Localization.no, role: .cancel) { } Button(Localization.yes, role: .destructive) { withAnimation { - homeActions.deleteAction(givenURL: card.givenURL) + homeActions.deleteAction( + givenURL: card.givenURL, + info: AnalyticsInfo( + type: card.type, + url: card.givenURL, + index: card.index + ) + ) } } } @@ -118,7 +125,15 @@ private extension CardFooter { inactiveColor: .ui.grey8 ) { Haptics.defaultTap() - homeActions.favoriteAction(isFavorite: isFavorite, givenURL: card.givenURL) + homeActions.favoriteAction( + isFavorite: isFavorite, + givenURL: card.givenURL, + info: AnalyticsInfo( + type: card.type, + url: card.givenURL, + index: card.index + ) + ) } .accessibilityIdentifier("favorite-button") } @@ -134,7 +149,16 @@ private extension CardFooter { activeColor: .ui.coral2 ) { Haptics.defaultTap() - homeActions.saveAction(isSaved: isSaved, givenURL: card.givenURL) + homeActions.saveAction( + isSaved: isSaved, + givenURL: card.givenURL, + info: AnalyticsInfo( + type: card.type, + url: card.givenURL, + index: card.index, + recommendationID: recommendationID + ) + ) } .accessibilityIdentifier("save-button") } @@ -143,10 +167,20 @@ private extension CardFooter { func makeOverflowMenu() -> some View { Menu { if card.enableArchiveMenuAction { - Button(action: { - Haptics.defaultTap() - homeActions.archiveAction(givenURL: card.givenURL) - }) { + Button( + action: { + Haptics.defaultTap() + homeActions.archiveAction( + givenURL: card.givenURL, + info: AnalyticsInfo( + type: card.type, + url: card.givenURL, + index: card.index, + recommendationID: recommendationID + ) + ) + } + ) { Label { Text(Localization.ItemAction.archive) } icon: { @@ -187,6 +221,16 @@ private extension CardFooter { if card.enableShareMenuAction { ShareableURLView(givenURL: card.givenURL, shareURL: shareURL) + .simultaneousGesture(TapGesture().onEnded { + homeActions.trackShare( + AnalyticsInfo( + type: card.type, + url: card.givenURL, + index: card.index, + recommendationID: recommendationID + ) + ) + }) } } label: { Image(asset: .overflow) diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Cards/CardView.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Cards/CardView.swift index d7ec1c5bf..5ca6835ce 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Cards/CardView.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Cards/CardView.swift @@ -60,9 +60,12 @@ struct CardView: View { .onAppear { homeActions .trackCardImpression( - card.type, - url: card.givenURL, - recommendationID: item?.recommendation?.analyticsID + AnalyticsInfo( + type: card.type, + url: card.givenURL, + index: card.index, + recommendationID: item?.recommendation?.analyticsID + ) ) } } @@ -135,10 +138,13 @@ private extension CardView { presentWebView = true } homeActions.trackCardContentOpen( - card.type, - url: card.givenURL, - recommendationID: item?.recommendation?.analyticsID, - externalDestination: externalDestination + AnalyticsInfo( + type: card.type, + url: card.givenURL, + index: card.index, + recommendationID: item?.recommendation?.analyticsID, + externalDestination: externalDestination + ) ) } } diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/Native Collections/CollectionStoriesView.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/Native Collections/CollectionStoriesView.swift index 10b458103..872bd4583 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/Native Collections/CollectionStoriesView.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/Native Collections/CollectionStoriesView.swift @@ -49,13 +49,14 @@ struct CollectionStoriesView: View { // MARK: helpers private extension CollectionStoriesView { var proposedCards: [HomeCardConfiguration] { - stories.compactMap { - if let item = $0.item { + stories.enumerated().compactMap { + if let item = $0.element.item { return HomeCardConfiguration( givenURL: item.givenURL, sharedWithYouUrlString: nil, showExcerpt: true, type: .collectionStory, + index: $0.offset, enableSaveAction: true, enableShareMenuAction: true, enableReportMenuAction: true diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/Native Collections/NativeCollectionView.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/Native Collections/NativeCollectionView.swift index 5f2d5aa17..a1f7f5f8d 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/Native Collections/NativeCollectionView.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/Native Collections/NativeCollectionView.swift @@ -74,7 +74,13 @@ struct NativeCollectionView: View { Button(Localization.no, role: .cancel) { } Button(Localization.yes, role: .destructive) { withAnimation { - homeActions.deleteAction(givenURL: destination.givenURL) + homeActions.deleteAction( + givenURL: destination.givenURL, + info: AnalyticsInfo( + type: .collection, + url: destination.givenURL + ) + ) } } } @@ -90,7 +96,7 @@ struct NativeCollectionView: View { highlightedColor: .ui.grey4, activeColor: .ui.black1 ) { - homeActions.saveAction(isSaved: isSaved, givenURL: givenURL) + homeActions.saveAction(isSaved: isSaved, givenURL: givenURL, info: AnalyticsInfo(type: .collection, url: givenURL)) if isSaved { navigation.back() } @@ -116,7 +122,7 @@ struct NativeCollectionView: View { if isSaved { Button(action: { Haptics.defaultTap() - homeActions.archiveAction(givenURL: destination.givenURL) + homeActions.archiveAction(givenURL: destination.givenURL, info: AnalyticsInfo(type: .collection, url: destination.givenURL)) navigation.back() }) { Label { @@ -158,6 +164,14 @@ struct NativeCollectionView: View { } ShareableURLView(givenURL: destination.givenURL, shareURL: item?.shareURL) + .simultaneousGesture(TapGesture().onEnded { + homeActions.trackShare( + AnalyticsInfo( + type: .collection, + url: destination.givenURL + ) + ) + }) } label: { Image(asset: .overflow) .homeOverflowMenyStyle() diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/SharedWithYouDetailView.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/SharedWithYouDetailView.swift index 9d4cd38f4..92107a125 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/SharedWithYouDetailView.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/SharedWithYouDetailView.swift @@ -38,12 +38,13 @@ struct SharedWithYouDetailView: View { // MARK: helpers private extension SharedWithYouDetailView { var proposedCards: [HomeCardConfiguration] { - sharedWithYouItems.compactMap { - if let item = $0.item { + sharedWithYouItems.enumerated().compactMap { + if let item = $0.element.item { return HomeCardConfiguration( givenURL: item.givenURL, - sharedWithYouUrlString: $0.url, + sharedWithYouUrlString: $0.element.url, type: .sharedWithYouDetail, + index: $0.offset, enableSaveAction: true, enableShareMenuAction: true, enableReportMenuAction: true diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/SlateDetailView.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/SlateDetailView.swift index b1842973f..b257697b7 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/SlateDetailView.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Detail views/SlateDetailView.swift @@ -45,12 +45,13 @@ struct SlateDetailView: View { // MARK: helpers private extension SlateDetailView { var proposedCards: [HomeCardConfiguration] { - recommendations.compactMap { - if let item = $0.item { + recommendations.enumerated().compactMap { + if let item = $0.element.item { return HomeCardConfiguration( givenURL: item.givenURL, sharedWithYouUrlString: nil, type: .slateDetail, + index: $0.offset, enableSaveAction: true, enableShareMenuAction: true, enableReportMenuAction: true diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/RecentSavesView.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/RecentSavesView.swift index 8519a9184..d032726dc 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/RecentSavesView.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/RecentSavesView.swift @@ -65,11 +65,12 @@ private extension RecentSavesView { } var proposedCards: [HomeCardConfiguration] { - savedItems.compactMap { + savedItems.enumerated().compactMap { HomeCardConfiguration( - givenURL: $0.item?.givenURL ?? $0.url, + givenURL: $0.element.item?.givenURL ?? $0.element.url, sharedWithYouUrlString: nil, type: .recentSave, + index: $0.offset, enableFavoriteAction: true, enableShareMenuAction: true, enableArchiveMenuAction: true, diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/RecommendationsView.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/RecommendationsView.swift index 0b02ff169..31f0b3b2a 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/RecommendationsView.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/RecommendationsView.swift @@ -41,6 +41,7 @@ private extension RecommendationsView { givenURL: item.givenURL, sharedWithYouUrlString: nil, type: .recommendation, + index: Int(item.recommendation?.sortIndex ?? 0), // sortIndex should not be nil, but just in case, let's have a default enableSaveAction: true, enableShareMenuAction: true, enableReportMenuAction: true diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/SharedWithYouView.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/SharedWithYouView.swift index c34227cb7..7979c4e10 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/SharedWithYouView.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Views/Top level views/SharedWithYouView.swift @@ -64,11 +64,12 @@ private extension SharedWithYouView { } var proposedCards: [HomeCardConfiguration] { - sharedWithYouItems.compactMap { + sharedWithYouItems.enumerated().compactMap { HomeCardConfiguration( - givenURL: $0.item?.givenURL ?? $0.url, - sharedWithYouUrlString: $0.url, + givenURL: $0.element.item?.givenURL ?? $0.element.url, + sharedWithYouUrlString: $0.element.url, type: .sharedWithYou, + index: $0.offset, enableSaveAction: true, enableShareMenuAction: true ) diff --git a/PocketKit/Sources/PocketKit/Report/ReportRecommendationView.swift b/PocketKit/Sources/PocketKit/Report/ReportRecommendationView.swift index 736078f62..97eb3f020 100644 --- a/PocketKit/Sources/PocketKit/Report/ReportRecommendationView.swift +++ b/PocketKit/Sources/PocketKit/Report/ReportRecommendationView.swift @@ -99,7 +99,7 @@ struct ReportRecommendationView: View { } // NOTE: As of 2/17/2023 The report view can only be called from the Home screen, so we assume that the SlateArticleReport event is the correct one. - tracker.track(event: Events.Home.SlateArticleReport(url: givenURL, reason: reason, recommendationId: recommendationId, comment: comment)) + tracker.track(event: Events.Home.slateArticleReport(url: givenURL, reason: reason, recommendationId: recommendationId, comment: comment)) } private func selectionColor(for reason: ReportEntity.Reason) -> Color { From 24238d2e07085ccd52a90917b8f38fae879860c0 Mon Sep 17 00:00:00 2001 From: Gio Date: Thu, 31 Oct 2024 21:12:17 -0500 Subject: [PATCH 4/5] fix favorite action --- .../Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift index 9d1ae98be..5ad4ae37d 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift @@ -42,10 +42,11 @@ struct HomeActions { let source = Services.shared.source if isFavorite { source.unFavorite(givenURL) + trackUnFavorite(info) } else { source.favorite(givenURL) + trackFavorite(info) } - trackFavorite(info) } func shareableUrl(shareURL: String?, givenURL: String) async -> String? { From 1bf4d4eff76ab5f5480fb9e55922978d208bbb14 Mon Sep 17 00:00:00 2001 From: Gio Date: Thu, 31 Oct 2024 21:18:56 -0500 Subject: [PATCH 5/5] fix some comments --- .../Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift index 5ad4ae37d..2050f5f23 100644 --- a/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift +++ b/PocketKit/Sources/PocketKit/Home/SwiftUI/Models/HomeActions.swift @@ -196,7 +196,7 @@ extension HomeActions { case .collection: tracker.track(event: Events.Collection.favoriteClicked(url: info.url)) default: - break // only recent saves can be favorited + break // only recent saves and collections can be favorited. Events from the reader are handled in UIKit for now. } } } @@ -210,7 +210,7 @@ extension HomeActions { case .collection: tracker.track(event: Events.Collection.unfavoriteClicked(url: info.url)) default: - break // only recent saves can be favorited. Events from the reader are handled in UIKit for now. + break // only recent saves and collections can be unfavorited. Events from the reader are handled in UIKit for now. } } }