Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add option to disable screen view usage #844

Merged
merged 5 commits into from
Jan 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Sources/Common/Type/ScreenView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// Enum to define how CustomerIO SDK should handle screen view events.
public enum ScreenView: String {
/// Screen view events are sent to destinations for analytics purposes.
/// They are also used to display in-app messages based on page rules.
case all

/// Screen view events are kept on device only. They are used to display in-app messages based on
/// page rules. Events are not sent to our back end servers.
case inApp = "inapp"

/// Returns the ScreenView enum case for the given name.
/// Returns fallback if the specified enum type has no constant with the given name.
/// Defaults to .all
public static func getScreenView(_ screenView: String?, fallback: ScreenView = .all) -> ScreenView {
guard let screenView = screenView,
!screenView.isEmpty,
let value = ScreenView(rawValue: screenView.lowercased())
else {
return fallback
}
return value
}
}
3 changes: 3 additions & 0 deletions Sources/DataPipeline/Config/DataPipelineConfigOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ public struct DataPipelineConfigOptions {
/// Configuration options required for migration from earlier versions
public let migrationSiteId: String?

/// Determines how SDK should handle screen view events
public let screenViewUse: ScreenView

/// Plugins identified based on configuration provided by the user
let autoConfiguredPlugins: [Plugin]
}
8 changes: 8 additions & 0 deletions Sources/DataPipeline/Config/SDKConfigBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class SDKConfigBuilder {
private var trackApplicationLifecycleEvents: Bool = true
private var autoTrackDeviceAttributes: Bool = true
private var migrationSiteId: String?
private var screenViewUse: ScreenView = .all

/// Initializes new `SDKConfigBuilder` with required configuration options.
/// - Parameters:
Expand Down Expand Up @@ -149,6 +150,12 @@ public class SDKConfigBuilder {
return self
}

@discardableResult
public func screenViewUse(screenView: ScreenView) -> SDKConfigBuilder {
screenViewUse = screenView
return self
}

@available(iOSApplicationExtension, unavailable)
public func build() -> SDKConfigBuilderResult {
// create `SdkConfig` from given configurations
Expand Down Expand Up @@ -180,6 +187,7 @@ public class SDKConfigBuilder {
trackApplicationLifecycleEvents: trackApplicationLifecycleEvents,
autoTrackDeviceAttributes: autoTrackDeviceAttributes,
migrationSiteId: migrationSiteId,
screenViewUse: screenViewUse,
autoConfiguredPlugins: configuredPlugins
)

Expand Down
3 changes: 3 additions & 0 deletions Sources/DataPipeline/DataPipelineImplementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ class DataPipelineImplementation: DataPipelineInstance {
// plugin to publish data pipeline events
analytics.add(plugin: DataPipelinePublishedEvents(diGraph: diGraph))

// Add plugin to filter events based on SDK configuration
analytics.add(plugin: ScreenFilterPlugin(screenViewUse: moduleConfig.screenViewUse))

// subscribe to journey events emmitted from push/in-app module to send them via datapipelines
subscribeToJourneyEvents()
postProfileAlreadyIdentified()
Expand Down
25 changes: 25 additions & 0 deletions Sources/DataPipeline/Plugins/ScreenFilterPlugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import CioAnalytics
import CioInternalCommon
/// Plugin to filter screen events based on the configuration provided by customer app.
/// This plugin is used to filter out screen events that should not be processed further.
class ScreenFilterPlugin: EventPlugin {
private let screenViewUse: ScreenView
public let type = PluginType.enrichment
public weak var analytics: Analytics?

init(screenViewUse: ScreenView) {
self.screenViewUse = screenViewUse
}

func screen(event: ScreenEvent) -> ScreenEvent? {
// Filter out screen events based on the configuration provided by customer app
// Using switch statement to enforce exhaustive checking for all possible values of ScreenView
switch screenViewUse {
case .all:
return event
// Do not send screen events to server if ScreenView is not Analytics
case .inApp:
return nil
}
}
}
1 change: 1 addition & 0 deletions Sources/DataPipeline/Type/Aliases.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ public typealias Metric = CioInternalCommon.Metric
public typealias CustomerIO = CioInternalCommon.CustomerIO
public typealias CioLogLevel = CioInternalCommon.CioLogLevel
public typealias Region = CioInternalCommon.Region
public typealias ScreenView = CioInternalCommon.ScreenView
40 changes: 40 additions & 0 deletions Tests/Common/Type/ScreenViewTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@testable import CioInternalCommon
import Foundation
import SharedTests
import XCTest

class ScreenViewTest: UnitTest {
func test_getScreenView_givenNamesWithMatchingCase_expectCorrectScreenView() {
let screenViewAnalytics = ScreenView.getScreenView("All")
let screenViewInApp = ScreenView.getScreenView("InApp")

XCTAssertEqual(screenViewAnalytics, .all)
XCTAssertEqual(screenViewInApp, .inApp)
}

func test_getScreenView_givenNamesWithDifferentCase_expectCorrectScreenView() {
let screenViewAnalytics = ScreenView.getScreenView("all")
let screenViewInApp = ScreenView.getScreenView("inapp")

XCTAssertEqual(screenViewAnalytics, .all)
XCTAssertEqual(screenViewInApp, .inApp)
}

func test_getScreenView_givenInvalidValue_expectFallbackScreenView() {
let parsedValue = ScreenView.getScreenView("none")

XCTAssertEqual(parsedValue, .all)
}

func test_getScreenView_givenEmptyValue_expectFallbackScreenView() {
let parsedValue = ScreenView.getScreenView("", fallback: .inApp)

XCTAssertEqual(parsedValue, .inApp)
}

func test_getScreenView_givenNil_expectFallbackScreenView() {
let parsedValue = ScreenView.getScreenView(nil)

XCTAssertEqual(parsedValue, .all)
}
}
70 changes: 70 additions & 0 deletions Tests/DataPipeline/Plugin/ScreenViewsFilterPluginTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
@testable import CioAnalytics
@testable import CioDataPipelines
@testable import CioInternalCommon
import Foundation
@testable import SharedTests
import XCTest

class ScreenViewFilterPluginTests: IntegrationTest {
var outputReader: OutputReaderPlugin!

override func setUp() {}

private func setupWithConfig(screenViewUse: ScreenView, customConfig: ((inout SdkConfig) -> Void)? = nil) {
super.setUp(modifySdkConfig: { config in
config.screenViewUse(screenView: screenViewUse)
})
outputReader = (customerIO.add(plugin: OutputReaderPlugin()) as? OutputReaderPlugin)
}

func testProcessGivenScreenViewUseAnalyticsExpectScreenEventWithoutPropertiesProcessed() {
setupWithConfig(screenViewUse: .all)

let givenScreenTitle = String.random

customerIO.screen(title: givenScreenTitle)

guard let screenEvent = outputReader.screenEvents.first, outputReader.screenEvents.count == 1 else {
XCTFail("Expected exactly one screen event")
return
}

XCTAssertEqual(screenEvent.name, givenScreenTitle)
XCTAssertTrue(screenEvent.properties?.dictionaryValue?.isEmpty ?? true)
}

func testProcessGivenScreenViewUseAnalyticsExpectScreenEventWithPropertiesProcessed() {
setupWithConfig(screenViewUse: .all)

let givenScreenTitle = String.random
let givenProperties: [String: Any] = [
"source": "push",
"discount": 10
]

customerIO.screen(title: givenScreenTitle, properties: givenProperties)

guard let screenEvent = outputReader.screenEvents.first, outputReader.screenEvents.count == 1 else {
XCTFail("Expected exactly one screen event")
return
}

XCTAssertEqual(screenEvent.name, givenScreenTitle)
XCTAssertMatches(
screenEvent.properties?.dictionaryValue,
givenProperties,
withTypeMap: [["discount"]: Int.self]
)
}

func testProcessGivenScreenViewUseInAppExpectAllScreenEventsIgnored() {
setupWithConfig(screenViewUse: .inApp)

// Track multiple screen events
for _ in 1 ... 5 {
customerIO.screen(title: String.random)
}

XCTAssertTrue(outputReader.events.isEmpty)
}
}
2 changes: 1 addition & 1 deletion Tests/DataPipeline/Support/Segment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class OutputReaderPlugin: Plugin {
extension OutputReaderPlugin {
var identifyEvents: [IdentifyEvent] { events.compactMap { $0 as? IdentifyEvent } }
var trackEvents: [RawEvent] { events.compactMap { $0 as? TrackEvent } }
var screenEvents: [RawEvent] { events.compactMap { $0 as? ScreenEvent } }
var screenEvents: [ScreenEvent] { events.compactMap { $0 as? ScreenEvent } }

var deviceDeleteEvents: [TrackEvent] {
events
Expand Down
Loading