-
Notifications
You must be signed in to change notification settings - Fork 172
feat: Add initial implementation of FlagsEvaluationContext and precomputed assignments support #2477
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
Merged
dd-mergequeue
merged 23 commits into
feature/flags
from
FFL-1014-Implement-FlagsEvaluationContext-and-use-to-request-precomputed-assignments
Sep 22, 2025
Merged
feat: Add initial implementation of FlagsEvaluationContext and precomputed assignments support #2477
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
c511ac9
feat: Add initial implementation of FlagsEvaluationContext and precom…
sameerank d3d786b
fix: Add missing newlines at the end of several source and test files
sameerank a6e4cfd
chore: Update TODO comment and clean up whitespace in Flags module files
sameerank a14fc18
chore: Update TODO comments and remove unnecessary whitespace in Flag…
sameerank 31a335b
chore: Clean up whitespace and formatting in Flags module test files
sameerank 6da7907
refactor: Introduce temporary FlagsConfiguration struct to resolve co…
sameerank 9996cdf
chore: Add Flags module source and test files to project configuration
sameerank 84cc10d
fix: Enhance mock HTTP client in FlagsClientTests with proper respons…
sameerank f6e9726
chore: Update TODO comments in Flags module to remove redundant refer…
sameerank 00632fe
fix: Changed FlagsEvaluationContext.attributes to a stricter type
sameerank 0e5554a
feat: Implement dedicated URLSession in NetworkFlagsHttpClient and en…
sameerank 707e6cd
feat: Add PrecomputeAssignmentsRequest model and integrate it into Fl…
sameerank 228299f
refactor: Replace site string with DatadogSite enum in FlagsClientCon…
sameerank 30dfee0
chore: Clean up whitespace and formatting in FlagsHttpClient and rela…
sameerank a4c9f21
FFL-1015 Fix tests coverage setup for `DatadogFlags`
ncreated a4dd1db
FFL-1015 Add `Flags.enable()` and register `FlagsFeature` to core
ncreated db5ca5f
FFLL-1015 Simplify FlagsClient config by using DatadogContext
ncreated 3ca1981
FFLL-1015 Add namespace to flags client configuration
ncreated 705fdbc
FFL-1015 CR feedback
ncreated 80fcedd
Merge pull request #2484 from DataDog/ncreated/FFL-1015/improve-flags…
dd-mergequeue[bot] dc1f7ba
Merge pull request #2480 from DataDog/ncreated/FFL-1015/enable-flags-…
dd-mergequeue[bot] f5ce5d0
refactor: Fix Swift naming convention violations for acronyms in Data…
sameerank 6fce004
chore: Remove FlagsHttpClient
sameerank File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| /* | ||
| * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. | ||
| * This product includes software developed at Datadog (https://www.datadoghq.com/). | ||
| * Copyright 2019-Present Datadog, Inc. | ||
| */ | ||
|
|
||
| import Foundation | ||
| import DatadogInternal | ||
|
|
||
| internal struct ExposureRequestBuilder: FeatureRequestBuilder { | ||
| /// A custom RUM intake. | ||
| let customIntakeURL: URL? | ||
| /// Telemetry interface. | ||
| let telemetry: Telemetry | ||
|
|
||
| func request(for events: [Event], with context: DatadogContext, execution: ExecutionContext) throws -> URLRequest { | ||
| guard let exposureEventData = events.first?.data else { | ||
| throw InternalError(description: "Found no event in Flags batch") | ||
| } | ||
|
|
||
| let builder = URLRequestBuilder( | ||
| url: url(with: context), | ||
| queryItems: [ | ||
| // TODO: FFL-1022 Send Exposure event data to /api/v2/exposures | ||
| ], | ||
| headers: [ | ||
| // TODO: FFL-1022 Send Exposure event data to /api/v2/exposures | ||
| ], | ||
| telemetry: telemetry | ||
| ) | ||
|
|
||
| return builder.uploadRequest( | ||
| with: exposureEventData, | ||
| compress: true // TODO: FFL-1022 If /api/v2/exposures supports compression | ||
| ) | ||
| } | ||
|
|
||
| private func url(with context: DatadogContext) -> URL { | ||
| customIntakeURL ?? context.site.endpoint.appendingPathComponent("api/v2/exposures") | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| /* | ||
| * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. | ||
| * This product includes software developed at Datadog (https://www.datadoghq.com/). | ||
| * Copyright 2019-Present Datadog, Inc. | ||
| */ | ||
|
|
||
| import Foundation | ||
| import DatadogInternal | ||
|
|
||
| internal struct FlagsFeature: DatadogRemoteFeature { | ||
| static let name = "flags" | ||
|
|
||
| let requestBuilder: FeatureRequestBuilder | ||
| let messageReceiver: FeatureMessageReceiver | ||
|
|
||
| let performanceOverride: PerformancePresetOverride | ||
|
|
||
| init( | ||
| configuration: Flags.Configuration, | ||
| featureScope: FeatureScope | ||
| ) { | ||
| requestBuilder = ExposureRequestBuilder( | ||
| customIntakeURL: configuration.customExposureEndpoint, | ||
| telemetry: featureScope.telemetry | ||
| ) | ||
| messageReceiver = NOPFeatureMessageReceiver() | ||
| performanceOverride = PerformancePresetOverride( | ||
| maxObjectsInFile: 1 // to send only one Exposure event per request | ||
| ) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| /* | ||
| * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. | ||
| * This product includes software developed at Datadog (https://www.datadoghq.com/). | ||
| * Copyright 2019-Present Datadog, Inc. | ||
| */ | ||
|
|
||
| import Foundation | ||
| import DatadogInternal | ||
|
|
||
| public class FlagsClient { | ||
| private let configuration: FlagsClient.Configuration | ||
| private let httpClient: FlagsHTTPClient | ||
| private let store: FlagsStore | ||
| private let featureScope: FeatureScope | ||
|
|
||
| internal init(configuration: FlagsClient.Configuration, httpClient: FlagsHTTPClient, store: FlagsStore, featureScope: FeatureScope) { | ||
| self.configuration = configuration | ||
| self.httpClient = httpClient | ||
| self.store = store | ||
| self.featureScope = featureScope | ||
| } | ||
|
|
||
| public static func create(with configuration: FlagsClient.Configuration, in core: DatadogCoreProtocol = CoreRegistry.default) -> FlagsClient { | ||
| do { | ||
| // To ensure the correct registration order between Core and Features, | ||
| // the entire initialization flow is synchronized on the main thread. | ||
| return try runOnMainThreadSync { | ||
| try createOrThrow(with: configuration, in: core) | ||
| } | ||
| } catch let error { | ||
| consolePrint("\(error)", .error) | ||
| fatalError("TODO: FFL-1016 Fallback to NOP Client") | ||
| } | ||
| } | ||
|
|
||
| internal static func createOrThrow(with configuration: FlagsClient.Configuration, in core: DatadogCoreProtocol) throws -> FlagsClient { | ||
| guard core.get(feature: FlagsFeature.self) != nil else { | ||
| throw ProgrammerError( | ||
| description: "`FlagsClient.create()` produces a non-functional client because the `Flags` feature was not enabled." | ||
| ) | ||
| } | ||
|
|
||
| let httpClient = NetworkFlagsHTTPClient() | ||
| let store = FlagsStore() | ||
| let featureScope = core.scope(for: FlagsFeature.self) | ||
| return FlagsClient(configuration: configuration, httpClient: httpClient, store: store, featureScope: featureScope) | ||
| } | ||
|
|
||
| public func setEvaluationContext(_ context: FlagsEvaluationContext, completion: @escaping (Result<Void, FlagsError>) -> Void) { | ||
| featureScope.context { [httpClient, configuration] sdkContext in | ||
| httpClient.postPrecomputeAssignments( | ||
| context: context, | ||
| configuration: configuration, | ||
| sdkContext: sdkContext | ||
| ) { [weak self] result in | ||
| guard let self = self else { | ||
| completion(.failure(.clientNotInitialized)) | ||
| return | ||
| } | ||
|
|
||
| switch result { | ||
| case .success(let (data, response)): | ||
| guard let httpResponse = response as? HTTPURLResponse, | ||
| 200...299 ~= httpResponse.statusCode else { | ||
| completion(.failure(.invalidResponse)) | ||
| return | ||
| } | ||
|
|
||
| do { | ||
| let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] | ||
|
|
||
| if let responseData = json?["data"] as? [String: Any], | ||
| let attributes = responseData["attributes"] as? [String: Any], | ||
| let flags = attributes["flags"] as? [String: Any] { | ||
| self.store.setFlags(flags, context: context) | ||
| completion(.success(())) | ||
| } else { | ||
| completion(.failure(.invalidResponse)) | ||
| } | ||
| } catch { | ||
| completion(.failure(.networkError(error))) | ||
| } | ||
|
|
||
| case .failure(let error): | ||
| completion(.failure(.networkError(error))) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public func getBooleanValue(key: String, defaultValue: Bool) -> Bool { | ||
| let flags = store.getFlags() | ||
| if let flagData = flags[key] as? [String: Any], | ||
| let value = flagData["variationValue"] as? Bool { | ||
| return value | ||
| } | ||
| return defaultValue | ||
| } | ||
|
|
||
| public func getStringValue(key: String, defaultValue: String) -> String { | ||
| let flags = store.getFlags() | ||
| if let flagData = flags[key] as? [String: Any], | ||
| let value = flagData["variationValue"] as? String { | ||
| return value | ||
| } | ||
| return defaultValue | ||
| } | ||
|
|
||
| public func getIntegerValue(key: String, defaultValue: Int64) -> Int64 { | ||
| let flags = store.getFlags() | ||
| if let flagData = flags[key] as? [String: Any], | ||
| let value = flagData["variationValue"] as? NSNumber { | ||
| return value.int64Value | ||
| } | ||
| return defaultValue | ||
| } | ||
|
|
||
| public func getDoubleValue(key: String, defaultValue: Double) -> Double { | ||
| let flags = store.getFlags() | ||
| if let flagData = flags[key] as? [String: Any], | ||
| let value = flagData["variationValue"] as? NSNumber { | ||
| return value.doubleValue | ||
| } | ||
| return defaultValue | ||
| } | ||
|
|
||
| // TODO: FFL-1047 Replace [String: Any] with OpenFeature.Value-compatible type | ||
| public func getObjectValue(key: String, defaultValue: [String: Any]) -> [String: Any] { | ||
| let flags = store.getFlags() | ||
| if let flagData = flags[key] as? [String: Any], | ||
| let value = flagData["variationValue"] as? [String: Any] { | ||
| return value | ||
| } | ||
| return defaultValue | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| /* | ||
| * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. | ||
| * This product includes software developed at Datadog (https://www.datadoghq.com/). | ||
| * Copyright 2019-Present Datadog, Inc. | ||
| */ | ||
|
|
||
| import Foundation | ||
| import DatadogInternal | ||
|
|
||
| extension FlagsClient { | ||
| public struct Configuration { | ||
| public let baseURL: String? | ||
| public let customHeaders: [String: String] | ||
| public let flaggingProxy: String? | ||
|
|
||
| public init( | ||
| baseURL: String? = nil, | ||
| customHeaders: [String: String] = [:], | ||
| flaggingProxy: String? = nil | ||
| ) { | ||
| self.baseURL = baseURL | ||
| self.customHeaders = customHeaders | ||
| self.flaggingProxy = flaggingProxy | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
non-blocking, comment/
[String: Any]is a pragmatic first choice, so let’s stick with it for now. That said, we should keepOpenFeature.Valuein mind - eventually, we’ll want to mirror this container in our SDK to ease migration from DatadogFlagsClienttoOpenFeatureClient. Let’s mark this as a "to do" (maybe add a JIRA?) 👍 and move on, WDYT?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed and TODO comment has been added 👍 https://datadoghq.atlassian.net/browse/FFL-1047