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

Always include traits in event payload, even if empty. #384

Merged
merged 5 commits into from
Mar 19, 2025
Merged
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
66 changes: 16 additions & 50 deletions .github/workflows/swift.yml
Original file line number Diff line number Diff line change
@@ -16,11 +16,11 @@ jobs:

generate_code_coverage:
needs: cancel_previous
runs-on: macos-14
runs-on: macos-15
steps:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "15.2"
xcode-version: "16.2"
- uses: actions/checkout@v2
- uses: webfactory/ssh-agent@v0.8.0
with:
@@ -37,55 +37,21 @@ jobs:

build_and_test_spm_mac:
needs: cancel_previous
runs-on: macos-14
runs-on: macos-15
steps:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "15.2"
xcode-version: "16.2"
- uses: actions/checkout@v2
- uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.SOVRAN_SSH_KEY }}
- name: Build & Run tests
run: swift test

build_and_test_spm_linux:
needs: cancel_previous
runs-on: ubuntu-latest
steps:
- uses: sersoft-gmbh/swifty-linux-action@v3
with:
release-version: "5.9.2"
github-token: ${{secrets.GITHUB_TOKEN}}
- uses: actions/checkout@v2
- uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.SOVRAN_SSH_KEY }}
- name: Build & Run tests
run: swift test --enable-test-discovery

build_and_test_spm_windows:
needs: cancel_previous
runs-on: windows-latest
steps:
- uses: SwiftyLab/setup-swift@latest
with:
swift-version: "5.10"
- uses: actions/checkout@v2
- name: Build
run: swift build
#
# Disable tests right now. There's an SPM issue where link errors generate
# a bad exit code even though the tests run/work properly.
#
# See: https://forums.swift.org/t/linker-warnings-on-windows-with-swift-argument-parser/71443/2
#
# - name: Run tests
# run: swift test --enable-test-discovery

build_and_test_ios:
needs: cancel_previous
runs-on: macos-14
runs-on: macos-15
steps:
- name: Install yeetd
run: |
@@ -94,20 +60,20 @@ jobs:
yeetd &
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "15.2"
xcode-version: "16.2"
- uses: actions/checkout@v2
- uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.SOVRAN_SSH_KEY }}
- run: xcodebuild -scheme Segment test -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15'
- run: xcodebuild -scheme Segment test -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 16'

build_and_test_tvos:
needs: cancel_previous
runs-on: macos-14
runs-on: macos-15
steps:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "15.2"
xcode-version: "16.2"
- uses: actions/checkout@v2
- uses: webfactory/ssh-agent@v0.8.0
with:
@@ -116,24 +82,24 @@ jobs:

build_and_test_watchos:
needs: cancel_previous
runs-on: macos-14
runs-on: macos-15
steps:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "15.2"
xcode-version: "16.2"
- uses: actions/checkout@v2
- uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.SOVRAN_SSH_KEY }}
- run: xcodebuild -scheme Segment test -sdk watchsimulator -destination 'platform=watchOS Simulator,name=Apple Watch Series 9 (45mm)'
- run: xcodebuild -scheme Segment test -sdk watchsimulator -destination 'platform=watchOS Simulator,name=Apple Watch Series 10 (42mm)'

build_and_test_visionos:
needs: cancel_previous
runs-on: macos-14
runs-on: macos-15
steps:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "15.2"
xcode-version: "16.2"
- uses: actions/checkout@v2
- uses: webfactory/ssh-agent@v0.8.0
with:
@@ -146,11 +112,11 @@ jobs:

build_and_test_examples:
needs: cancel_previous
runs-on: macos-14
runs-on: macos-15
steps:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "15.2"
xcode-version: "16.2"
- uses: actions/checkout@v2
- uses: webfactory/ssh-agent@v0.8.0
with:
18 changes: 0 additions & 18 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
{
"pins" : [
{
"identity" : "analytics-swift-mixpanel",
"kind" : "remoteSourceControl",
"location" : "https://github.com/segment-integrations/analytics-swift-mixpanel",
"state" : {
"revision" : "bc6a9628af225e679a581cc9ac2316eaf42f23a8",
"version" : "1.1.7"
}
},
{
"identity" : "jsonsafeencoding-swift",
"kind" : "remoteSourceControl",
@@ -18,15 +9,6 @@
"version" : "2.0.0"
}
},
{
"identity" : "mixpanel-swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/mixpanel/mixpanel-swift",
"state" : {
"revision" : "48d6668ceaaefc338f94e2b084c3cf77b90182f8",
"version" : "4.3.0"
}
},
{
"identity" : "sovran-swift",
"kind" : "remoteSourceControl",
4 changes: 2 additions & 2 deletions Sources/Segment/Analytics.swift
Original file line number Diff line number Diff line change
@@ -241,15 +241,15 @@ extension Analytics {
/// Returns the traits that were specified in the last identify call.
public func traits<T: Codable>() -> T? {
if let userInfo: UserInfo = store.currentState() {
return userInfo.traits?.codableValue()
return userInfo.traits.codableValue()
}
return nil
}

/// Returns the traits that were specified in the last identify call, as a dictionary.
public func traits() -> [String: Any]? {
if let userInfo: UserInfo = store.currentState() {
return userInfo.traits?.dictionaryValue
return userInfo.traits.dictionaryValue
}
return nil
}
10 changes: 5 additions & 5 deletions Sources/Segment/State.swift
Original file line number Diff line number Diff line change
@@ -108,7 +108,7 @@ struct System: State {
struct UserInfo: Codable, State {
let anonymousId: String
let userId: String?
let traits: JSON?
let traits: JSON
let referrer: URL?

@Noncodable var anonIdGenerator: AnonymousIdGenerator?
@@ -121,7 +121,7 @@ struct UserInfo: Codable, State {
} else {
anonId = UUID().uuidString
}
return UserInfo(anonymousId: anonId, userId: nil, traits: nil, referrer: nil, anonIdGenerator: state.anonIdGenerator)
return UserInfo(anonymousId: anonId, userId: nil, traits: .object([:]), referrer: nil, anonIdGenerator: state.anonIdGenerator)
}
}

@@ -137,7 +137,7 @@ struct UserInfo: Codable, State {
let traits: JSON?

func reduce(state: UserInfo) -> UserInfo {
return UserInfo(anonymousId: state.anonymousId, userId: state.userId, traits: traits, referrer: state.referrer, anonIdGenerator: state.anonIdGenerator)
return UserInfo(anonymousId: state.anonymousId, userId: state.userId, traits: traits ?? .object([:]), referrer: state.referrer, anonIdGenerator: state.anonIdGenerator)
}
}

@@ -146,7 +146,7 @@ struct UserInfo: Codable, State {
let traits: JSON?

func reduce(state: UserInfo) -> UserInfo {
return UserInfo(anonymousId: state.anonymousId, userId: userId, traits: traits, referrer: state.referrer, anonIdGenerator: state.anonIdGenerator)
return UserInfo(anonymousId: state.anonymousId, userId: userId, traits: traits ?? .object([:]), referrer: state.referrer, anonIdGenerator: state.anonIdGenerator)
}
}

@@ -178,7 +178,7 @@ extension System {
extension UserInfo {
static func defaultState(from storage: Storage, anonIdGenerator: AnonymousIdGenerator) -> UserInfo {
let userId: String? = storage.read(.userId)
let traits: JSON? = storage.read(.traits)
let traits: JSON = storage.read(.traits) ?? .object([:])
var anonymousId: String
if let existingId: String = storage.read(.anonymousId) {
anonymousId = existingId
15 changes: 14 additions & 1 deletion Tests/Segment-Tests/Analytics_Tests.swift
Original file line number Diff line number Diff line change
@@ -347,21 +347,34 @@ final class Analytics_Tests: XCTestCase {
}

func testIdentify() {
Storage.hardSettingsReset(writeKey: "test")
let analytics = Analytics(configuration: Configuration(writeKey: "test"))
let outputReader = OutputReaderPlugin()
analytics.add(plugin: outputReader)

waitUntilStarted(analytics: analytics)

// traits should be an empty object.
let currentTraits = analytics.traits()
XCTAssertNotNil(currentTraits)
XCTAssertTrue(currentTraits!.isEmpty == true)

analytics.identify(userId: "brandon", traits: MyTraits(email: "blah@blah.com"))

let identifyEvent: IdentifyEvent? = outputReader.lastEvent as? IdentifyEvent
XCTAssertTrue(identifyEvent?.userId == "brandon")
let traits = identifyEvent?.traits?.dictionaryValue
XCTAssertTrue(traits?["email"] as? String == "blah@blah.com")

analytics.reset()

let emptyTraits = analytics.traits()
XCTAssertNotNil(emptyTraits)
XCTAssertTrue(emptyTraits!.isEmpty == true)
}

func testUserIdAndTraitsPersistCorrectly() {
Storage.hardSettingsReset(writeKey: "test")
let analytics = Analytics(configuration: Configuration(writeKey: "test"))
let outputReader = OutputReaderPlugin()
analytics.add(plugin: outputReader)
@@ -693,7 +706,7 @@ final class Analytics_Tests: XCTestCase {
return request
}.errorHandler { error in
switch error {
case AnalyticsError.networkServerRejected(_):
case AnalyticsError.networkServerRejected(_, _):
// we expect this one; it's a bogus writekey
break;
default:
2 changes: 1 addition & 1 deletion Tests/Segment-Tests/JSON_Tests.swift
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@ class JSONTests: XCTestCase {
}

func testJSONBasic() throws {
let traits = try? JSON(["email": "blah@blah.com"])
let traits = try! JSON(["email": "blah@blah.com"])
let userInfo = UserInfo(anonymousId: "1234", userId: "brandon", traits: traits, referrer: nil)

let encoder = JSONSafeEncoder.default