Skip to content

Commit

Permalink
prepare 8.0.0 release (#286)
Browse files Browse the repository at this point in the history
## [8.0.0] - 2022-12-07
The latest version of this SDK supports LaunchDarkly's new custom contexts feature. Contexts are an evolution of a previously-existing concept, "users." Contexts let you create targeting rules for feature flags based on a variety of different information, including attributes pertaining to users, organizations, devices, and more. You can even combine contexts to create "multi-contexts."

This feature is only available to members of LaunchDarkly's Early Access Program (EAP). If you're in the EAP, you can use contexts by updating your SDK to the latest version and, if applicable, updating your Relay Proxy. Outdated SDK versions do not support contexts, and will cause unpredictable flag evaluation behavior.

If you are not in the EAP, only use single contexts of kind "user", or continue to use the user type if available. If you try to create contexts, the context will be sent to LaunchDarkly, but any data not related to the user object will be ignored.

For detailed information about this version, please refer to the list below. For information on how to upgrade from the previous version, please read the migration guide for [Swift](https://docs.launchdarkly.com/sdk/client-side/ios/migration-7-to-8-swift) or [Objective-C](https://docs.launchdarkly.com/sdk/client-side/ios/migration-7-to-8-objc).

### Added:
- The type `LDContext` defines the new context model.
- For all SDK methods that took an `LDUser` parameter, there is now an overload that takes an `LDContext`.

### Changed:
- The `secondary` attribute which existed in `LDUser` is no longer a supported feature. If you set an attribute with that name in `LDContext`, it will simply be a custom attribute like any other.
- Analytics event data now uses a new JSON schema due to differences between the context model and the old user model.
- The SDK no longer adds `device` and `os` values to the user attributes. Applications that wish to use device/OS information in feature flag rules must explicitly add such information.

### Removed:
- Removed the `secondary` meta-attribute in `LDUser`.
- The `alias` method no longer exists because alias events are not needed in the new context model.
- The `autoAliasingOptOut` and `inlineUsersInEvents` options no longer exist because they are not relevant in the new context model.
  • Loading branch information
keelerm84 authored Dec 7, 2022
1 parent 8ceaa14 commit b630147
Show file tree
Hide file tree
Showing 70 changed files with 3,523 additions and 1,068 deletions.
9 changes: 7 additions & 2 deletions .jazzy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ custom_categories:
children:
- LDClient
- LDConfig
- LDContext
- LDContextBuilder
- LDUser
- Reference
- LDMultiContextBuilder
- LDEvaluationDetail
- LDValue

Expand All @@ -36,7 +40,6 @@ custom_categories:

- name: Other Types
children:
- UserAttribute
- LDStreamingMode
- LDFlagKey
- LDInvalidArgumentError
Expand All @@ -46,8 +49,10 @@ custom_categories:
children:
- ObjcLDClient
- ObjcLDConfig
- ObjcLDUser
- ObjcLDReference
- ObjcLDContext
- ObjcLDChangedFlag
- ObjcLDUser
- ObjcLDValue
- ObjcLDValueType

Expand Down
9 changes: 8 additions & 1 deletion .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

disabled_rules:
- line_length
- trailing_whitespace

opt_in_rules:
- contains_over_filter_count
Expand Down Expand Up @@ -57,4 +56,12 @@ identifier_name:
- lhs
- rhs

trailing_whitespace:
severity: error

missing_docs:
error:
- open
- public

reporter: "xcode"
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@

All notable changes to the LaunchDarkly iOS SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).

## [8.0.0] - 2022-12-07
The latest version of this SDK supports LaunchDarkly's new custom contexts feature. Contexts are an evolution of a previously-existing concept, "users." Contexts let you create targeting rules for feature flags based on a variety of different information, including attributes pertaining to users, organizations, devices, and more. You can even combine contexts to create "multi-contexts."

This feature is only available to members of LaunchDarkly's Early Access Program (EAP). If you're in the EAP, you can use contexts by updating your SDK to the latest version and, if applicable, updating your Relay Proxy. Outdated SDK versions do not support contexts, and will cause unpredictable flag evaluation behavior.

If you are not in the EAP, only use single contexts of kind "user", or continue to use the user type if available. If you try to create contexts, the context will be sent to LaunchDarkly, but any data not related to the user object will be ignored.

For detailed information about this version, please refer to the list below. For information on how to upgrade from the previous version, please read the migration guide for [Swift](https://docs.launchdarkly.com/sdk/client-side/ios/migration-7-to-8-swift) or [Objective-C](https://docs.launchdarkly.com/sdk/client-side/ios/migration-7-to-8-objc).

### Added:
- The type `LDContext` defines the new context model.
- For all SDK methods that took an `LDUser` parameter, there is now an overload that takes an `LDContext`.

### Changed:
- The `secondary` attribute which existed in `LDUser` is no longer a supported feature. If you set an attribute with that name in `LDContext`, it will simply be a custom attribute like any other.
- Analytics event data now uses a new JSON schema due to differences between the context model and the old user model.
- The SDK no longer adds `device` and `os` values to the user attributes. Applications that wish to use device/OS information in feature flag rules must explicitly add such information.

### Removed:
- Removed the `secondary` meta-attribute in `LDUser`.
- The `alias` method no longer exists because alias events are not needed in the new context model.
- The `autoAliasingOptOut` and `inlineUsersInEvents` options no longer exist because they are not relevant in the new context model.

## [7.1.0] - 2022-11-08
### Added:
- Added Objective C bindings for ApplicationInfo.
Expand Down
19 changes: 15 additions & 4 deletions ContractTests/.swiftlint.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# See test subconfiguration at `LaunchDarkly/LaunchDarklyTests/.swiftlint.yml`

disabled_rules:
- cyclomatic_complexity
- line_length
- trailing_whitespace
- todo

opt_in_rules:
- contains_over_filter_count
Expand All @@ -26,8 +29,8 @@ included:
excluded:

function_body_length:
warning: 50
error: 70
warning: 70
error: 90

type_body_length:
warning: 300
Expand All @@ -39,7 +42,7 @@ file_length:

identifier_name:
min_length: # only min_length
warning: 3 # only warning
warning: 2 # only warning
max_length:
warning: 50
error: 60
Expand All @@ -54,4 +57,12 @@ identifier_name:
- lhs
- rhs

trailing_whitespace:
severity: error

missing_docs:
error:
- open
- public

reporter: "xcode"
2 changes: 1 addition & 1 deletion ContractTests/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ let package = Package(
.product(name: "LaunchDarkly", package: "LaunchDarkly"),
.product(name: "Vapor", package: "vapor")
],
path: "Source"),
path: "Source")
],
swiftLanguageVersions: [.v5])
112 changes: 94 additions & 18 deletions ContractTests/Source/Controllers/SdkController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Vapor
import LaunchDarkly

final class SdkController: RouteCollection {
private var clients: [Int : LDClient] = [:]
private var clients: [Int: LDClient] = [:]
private var clientCounter = 0

func boot(routes: RoutesBuilder) {
Expand All @@ -20,7 +20,9 @@ final class SdkController: RouteCollection {
"client-side",
"mobile",
"service-endpoints",
"tags"
"strongly-typed",
"tags",
"user-type"
]

return StatusResponse(
Expand Down Expand Up @@ -61,20 +63,16 @@ final class SdkController: RouteCollection {
}

if let allPrivate = events.allAttributesPrivate {
config.allUserAttributesPrivate = allPrivate
config.allContextAttributesPrivate = allPrivate
}

if let globalPrivate = events.globalPrivateAttributes {
config.privateUserAttributes = globalPrivate.map({ UserAttribute.forName($0) })
config.privateContextAttributes = globalPrivate.map { Reference($0) }
}

if let flushIntervalMs = events.flushIntervalMs {
config.eventFlushInterval = flushIntervalMs
}

if let inlineUsers = events.inlineUsers {
config.inlineUserInEvents = inlineUsers
}
}

if let tags = createInstance.configuration.tags {
Expand All @@ -92,10 +90,6 @@ final class SdkController: RouteCollection {

let clientSide = createInstance.configuration.clientSide

if let autoAliasingOptOut = clientSide.autoAliasingOptOut {
config.autoAliasingOptOut = autoAliasingOptOut
}

if let evaluationReasons = clientSide.evaluationReasons {
config.evaluationReasons = evaluationReasons
}
Expand All @@ -107,8 +101,14 @@ final class SdkController: RouteCollection {
let dispatchSemaphore = DispatchSemaphore(value: 0)
let startWaitSeconds = (createInstance.configuration.startWaitTimeMs ?? 5_000) / 1_000

LDClient.start(config:config, user: clientSide.initialUser, startWaitSeconds: startWaitSeconds) { timedOut in
dispatchSemaphore.signal()
if let context = clientSide.initialContext {
LDClient.start(config: config, context: context, startWaitSeconds: startWaitSeconds) { _ in
dispatchSemaphore.signal()
}
} else if let user = clientSide.initialUser {
LDClient.start(config: config, user: user, startWaitSeconds: startWaitSeconds) { _ in
dispatchSemaphore.signal()
}
}

dispatchSemaphore.wait()
Expand Down Expand Up @@ -159,24 +159,100 @@ final class SdkController: RouteCollection {
return CommandResponse.evaluateAll(result)
case "identifyEvent":
let semaphore = DispatchSemaphore(value: 0)
client.identify(user: commandParameters.identifyEvent!.user) {
semaphore.signal()
if let context = commandParameters.identifyEvent!.context {
client.identify(context: context) {
semaphore.signal()
}
} else if let user = commandParameters.identifyEvent!.user {
client.identify(user: user) {
semaphore.signal()
}
}
semaphore.wait()
case "aliasEvent":
client.alias(context: commandParameters.aliasEvent!.user, previousContext: commandParameters.aliasEvent!.previousUser)
case "customEvent":
let event = commandParameters.customEvent!
client.track(key: event.eventKey, data: event.data, metricValue: event.metricValue)
case "flushEvents":
client.flush()
case "contextBuild":
let contextBuild = commandParameters.contextBuild!

do {
if let singleParams = contextBuild.single {
let context = try SdkController.buildSingleContextFromParams(singleParams)

let encoder = JSONEncoder()
let output = try encoder.encode(context)

let response = ContextBuildResponse(output: String(data: Data(output), encoding: .utf8))
return CommandResponse.contextBuild(response)
}

if let multiParams = contextBuild.multi {
var multiContextBuilder = LDMultiContextBuilder()
try multiParams.forEach {
multiContextBuilder.addContext(try SdkController.buildSingleContextFromParams($0))
}

let context = try multiContextBuilder.build().get()
let encoder = JSONEncoder()
let output = try encoder.encode(context)

let response = ContextBuildResponse(output: String(data: Data(output), encoding: .utf8))
return CommandResponse.contextBuild(response)
}
} catch {
let response = ContextBuildResponse(output: nil, error: error.localizedDescription)
return CommandResponse.contextBuild(response)
}
case "contextConvert":
let convertRequest = commandParameters.contextConvert!
do {
let decoder = JSONDecoder()
let context: LDContext = try decoder.decode(LDContext.self, from: Data(convertRequest.input.utf8))

let encoder = JSONEncoder()
let output = try encoder.encode(context)

let response = ContextBuildResponse(output: String(data: Data(output), encoding: .utf8))
return CommandResponse.contextBuild(response)
} catch {
let response = ContextBuildResponse(output: nil, error: error.localizedDescription)
return CommandResponse.contextBuild(response)
}
default:
throw Abort(.badRequest)
}

return CommandResponse.ok
}

static func buildSingleContextFromParams(_ params: SingleContextParameters) throws -> LDContext {
var contextBuilder = LDContextBuilder(key: params.key)

if let kind = params.kind {
contextBuilder.kind(kind)
}

if let name = params.name {
contextBuilder.name(name)
}

if let anonymous = params.anonymous {
contextBuilder.anonymous(anonymous)
}

if let privateAttributes = params.privateAttribute {
privateAttributes.forEach { contextBuilder.addPrivateAttribute(Reference($0)) }
}

if let custom = params.custom {
custom.forEach { contextBuilder.trySetValue($0.key, $0.value) }
}

return try contextBuilder.build().get()
}

func evaluate(_ client: LDClient, _ params: EvaluateFlagParameters) throws -> EvaluateFlagResponse {
switch params.valueType {
case "bool":
Expand Down
7 changes: 4 additions & 3 deletions ContractTests/Source/Models/client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ struct Configuration: Content {
var credential: String
var startWaitTimeMs: Double?
var initCanFail: Bool?
// TODO(mmk) Add serviceEndpoints
var streaming: StreamingParameters?
var polling: PollingParameters?
var events: EventParameters?
Expand All @@ -24,6 +25,7 @@ struct StreamingParameters: Content {

struct PollingParameters: Content {
var baseUri: String?
// TODO(mmk) Add pollIntervalMs
}

struct EventParameters: Content {
Expand All @@ -33,7 +35,6 @@ struct EventParameters: Content {
var allAttributesPrivate: Bool?
var globalPrivateAttributes: [String]?
var flushIntervalMs: Double?
var inlineUsers: Bool?
}

struct TagParameters: Content {
Expand All @@ -42,8 +43,8 @@ struct TagParameters: Content {
}

struct ClientSideParameters: Content {
var initialUser: LDUser
var autoAliasingOptOut: Bool?
var initialContext: LDContext?
var initialUser: LDUser?
var evaluationReasons: Bool?
var useReport: Bool?
}
Loading

0 comments on commit b630147

Please sign in to comment.