Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion Segment.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@
attributes = {
LastSwiftMigration = 9999;
LastSwiftUpdateCheck = 1220;
LastUpgradeCheck = 1250;
LastUpgradeCheck = 1310;
};
buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "Segment" */;
compatibilityVersion = "Xcode 3.2";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1250"
LastUpgradeVersion = "1310"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
9 changes: 9 additions & 0 deletions Sources/Segment/Analytics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ extension Analytics {
return settings
}

/// Manually enable a destination plugin. This is useful when a given DestinationPlugin doesn't have any Segment tie-ins at all.
/// This will allow the destination to be processed in the same way within this library.
/// - Parameters:
/// - plugin: The destination plugin to enable.
public func manuallyEnableDestination(plugin: DestinationPlugin) {
self.store.dispatch(action: System.AddDestinationToSettingsAction(key: plugin.key))
}


public func version() -> String {
return Analytics.version()
}
Expand Down
8 changes: 0 additions & 8 deletions Sources/Segment/Plugins.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,6 @@ extension Analytics {
public func add(plugin: Plugin) -> Plugin {
plugin.configure(analytics: self)
timeline.add(plugin: plugin)
if !(plugin is SegmentDestination), let destPlugin = plugin as? DestinationPlugin {
// need to maintain the list of integrations to inject into payload
store.dispatch(action: System.AddIntegrationAction(key: destPlugin.key))
}

return plugin
}

Expand All @@ -155,9 +150,6 @@ extension Analytics {
*/
public func remove(plugin: Plugin) {
timeline.remove(plugin: plugin)
if !(plugin is SegmentDestination), let destPlugin = plugin as? DestinationPlugin {
store.dispatch(action: System.RemoveIntegrationAction(key: destPlugin.key))
}
}

public func find<T: Plugin>(pluginType: T.Type) -> T? {
Expand Down
53 changes: 30 additions & 23 deletions Sources/Segment/Plugins/SegmentDestination.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,29 +70,13 @@ public class SegmentDestination: DestinationPlugin {
}

// MARK: - Event Handling Methods
public func identify(event: IdentifyEvent) -> IdentifyEvent? {
queueEvent(event: event)
return event
}

public func track(event: TrackEvent) -> TrackEvent? {
queueEvent(event: event)
return event
}

public func screen(event: ScreenEvent) -> ScreenEvent? {
queueEvent(event: event)
return event
}

public func alias(event: AliasEvent) -> AliasEvent? {
queueEvent(event: event)
return event
}

public func group(event: GroupEvent) -> GroupEvent? {
queueEvent(event: event)
return event
public func execute<T: RawEvent>(event: T?) -> T? {
let result: T? = event
if let r = result {
let modified = configureCloudDestinations(event: r)
queueEvent(event: modified)
}
return result
}

// MARK: - Abstracted Lifecycle Methods
Expand Down Expand Up @@ -156,6 +140,29 @@ public class SegmentDestination: DestinationPlugin {
}
}

// MARK: - Utility methods
extension SegmentDestination {
internal func configureCloudDestinations<T: RawEvent>(event: T) -> T {
guard let integrationSettings = analytics?.settings() else { return event }
guard let plugins = analytics?.timeline.plugins[.destination]?.plugins as? [DestinationPlugin] else { return event }
guard let customerValues = event.integrations?.dictionaryValue else { return event }

// take the customer values first.
var merged = customerValues
// compare settings to loaded plugins
for plugin in plugins {
let hasSettings = integrationSettings.hasIntegrationSettings(forPlugin: plugin)
if hasSettings {
// we have a device mode plugin installed.
// tell segment not to send it via cloud mode.
merged[plugin.key] = false
}
}

return event
}
}

// MARK: - Upload management

extension SegmentDestination {
Expand Down
17 changes: 3 additions & 14 deletions Sources/Segment/Settings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,9 @@ public struct Settings: Codable {
return integrationSettings(forKey: plugin.key)
}

public func isDestinationEnabled(key: String) -> Bool {
public func hasIntegrationSettings(forPlugin plugin: DestinationPlugin) -> Bool {
guard let settings = integrations?.dictionaryValue else { return false }
if settings.keys.contains(key) {
return true
}
return false
return (settings[plugin.key] != nil)
}
}

Expand All @@ -85,15 +82,7 @@ extension Settings: Equatable {
}
}

extension Analytics {
/// Manually enable a destination plugin. This is useful when a given DestinationPlugin doesn't have any Segment tie-ins at all.
/// This will allow the destination to be processed in the same way within this library.
/// - Parameters:
/// - plugin: The destination plugin to enable.
public func manuallyEnableDestination(plugin: DestinationPlugin) {
self.store.dispatch(action: System.AddIntegrationAction(key: plugin.key))
}

extension Analytics {
internal func update(settings: Settings, type: UpdateType) {
apply { (plugin) in
// tell all top level plugins to update.
Expand Down
56 changes: 14 additions & 42 deletions Sources/Segment/State.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import Sovran

struct System: State {
let configuration: Configuration
let integrations: JSON?
let settings: Settings?
let running: Bool

Expand All @@ -21,60 +20,34 @@ struct System: State {

func reduce(state: System) -> System {
let result = System(configuration: state.configuration,
integrations: state.integrations,
settings: settings,
running: state.running)
return result
}
}

struct AddIntegrationAction: Action {
let key: String
struct ToggleRunningAction: Action {
let running: Bool

func reduce(state: System) -> System {
// we need to set any destination plugins to false in the
// integrations payload. this prevents them from being sent
// by segment.com once an event reaches segment.
if var integrations = state.integrations?.dictionaryValue {
integrations[key] = false
if let jsonIntegrations = try? JSON(integrations) {
let result = System(configuration: state.configuration,
integrations: jsonIntegrations,
settings: state.settings,
running: state.running)
return result
}
}
return state
return System(configuration: state.configuration,
settings: state.settings,
running: running)
}
}

struct RemoveIntegrationAction: Action {
struct AddDestinationToSettingsAction: Action {
let key: String

func reduce(state: System) -> System {
if var integrations = state.integrations?.dictionaryValue {
integrations.removeValue(forKey: key)
if let jsonIntegrations = try? JSON(integrations) {
let result = System(configuration: state.configuration,
integrations: jsonIntegrations,
settings: state.settings,
running: state.running)
return result
}
var settings = state.settings
if var integrations = settings?.integrations?.dictionaryValue {
integrations[key] = true
settings?.integrations = try? JSON(integrations)
}
return state
}
}

struct ToggleRunningAction: Action {
let running: Bool

func reduce(state: System) -> System {
return System(configuration: state.configuration,
integrations: state.integrations,
settings: state.settings,
running: running)
settings: settings,
running: state.running)
}
}
}
Expand Down Expand Up @@ -139,8 +112,7 @@ extension System {
settings = Settings(writeKey: configuration.values.writeKey, apiHost: HTTPClient.getDefaultAPIHost())
}
}
let integrationDictionary = try! JSON([String: Any]())
return System(configuration: configuration, integrations: integrationDictionary, settings: settings, running: false)
return System(configuration: configuration, settings: settings, running: false)
}
}

Expand Down
20 changes: 15 additions & 5 deletions Sources/Segment/Timeline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -210,18 +210,28 @@ extension DestinationPlugin {
}
return result
}

internal func isDestinationEnabled(integrations: JSON?) -> Bool {
var customerDisabled = false
if let disabled: Bool = integrations?.value(forKeyPath: KeyPath(self.key)), disabled == false {
customerDisabled = true
}

var hasSettings = false
if let settings = analytics?.settings() {
hasSettings = settings.hasIntegrationSettings(forPlugin: self)
}

return (hasSettings && customerDisabled == false)
}

internal func process<E: RawEvent>(incomingEvent: E) -> E? {
// This will process plugins (think destination middleware) that are tied
// to this destination.

var result: E? = nil

// For destination plugins, we will always have some kind of `settings`,
// and if we don't, it means this destination hasn't been setup on app.segment.com,
// which in turn ALSO means that we shouldn't be sending events to it.

if let enabled = analytics?.settings()?.isDestinationEnabled(key: self.key), enabled == true {
if isDestinationEnabled(integrations: incomingEvent.integrations) {
// apply .before and .enrichment types first ...
let beforeResult = timeline.applyPlugins(type: .before, event: incomingEvent)
let enrichmentResult = timeline.applyPlugins(type: .enrichment, event: beforeResult)
Expand Down
3 changes: 1 addition & 2 deletions Sources/Segment/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -277,14 +277,13 @@ extension RawEvent {
internal func applyRawEventData(store: Store) -> Self {
var result: Self = self

guard let system: System = store.currentState() else { return self }
guard let userInfo: UserInfo = store.currentState() else { return self }

result.anonymousId = userInfo.anonymousId
result.userId = userInfo.userId
result.messageId = UUID().uuidString
result.timestamp = Date().iso8601()
result.integrations = system.integrations
result.integrations = try? JSON([String: Any]())

return result
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/Segment-Tests/Analytics_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ final class Analytics_Tests: XCTestCase {
UserDefaults.standard.removePersistentDomain(forName: "com.segment.storage.test")

let expectation = XCTestExpectation(description: "MyDestination Expectation")
let myDestination = MyDestination {
let myDestination = MyDestination(disabled: true) {
expectation.fulfill()
return true
}
Expand Down
10 changes: 8 additions & 2 deletions Tests/Segment-Tests/Support/TestUtilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,21 @@ class MyDestination: DestinationPlugin {
var analytics: Analytics?
let trackCompletion: (() -> Bool)?

init(trackCompletion: (() -> Bool)? = nil) {
let disabled: Bool

init(disabled: Bool = false, trackCompletion: (() -> Bool)? = nil) {
self.key = "MyDestination"
self.type = .destination
self.timeline = Timeline()
self.trackCompletion = trackCompletion
self.disabled = disabled
}

func update(settings: Settings, type: UpdateType) {
//
if disabled == false {
// add ourselves to the settings
analytics?.manuallyEnableDestination(plugin: self)
}
}

func track(event: TrackEvent) -> TrackEvent? {
Expand Down
10 changes: 0 additions & 10 deletions Tests/Segment-Tests/Timeline_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,7 @@ class Timeline_Tests: XCTestCase {
return true
}

// Do this to force enable the destination
var settings = Settings(writeKey: "test")
if let existing = settings.integrations?.dictionaryValue {
var newIntegrations = existing
newIntegrations[firstDestination.key] = true
settings.integrations = try! JSON(newIntegrations)
}
let configuration = Configuration(writeKey: "test")
configuration.defaultSettings(settings)


let analytics = Analytics(configuration: configuration)

analytics.add(plugin: firstDestination)
Expand Down