Skip to content

Commit

Permalink
feat: add option to disable screen view usage (#844)
Browse files Browse the repository at this point in the history
  • Loading branch information
Shahroz16 authored Jan 1, 2025
1 parent 1f0ce7f commit 486e228
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 1 deletion.
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

0 comments on commit 486e228

Please sign in to comment.