Skip to content

Commit

Permalink
prepare 8.3.0 release (#189)
Browse files Browse the repository at this point in the history
## [8.3.0] - 2024-04-03
### Added:
- This release introduces a Hooks API. Hooks are collections of
user-defined callbacks that are executed by the SDK at various points of
interest. You can use them to augment the SDK with metrics or tracing.

### Changed:
- Dropped explicit support for .NET Core 3.1.
  • Loading branch information
cwaldren-ld committed Apr 3, 2024
2 parents 4e4107f + 18c98f2 commit 7c1c9ab
Show file tree
Hide file tree
Showing 32 changed files with 1,369 additions and 39 deletions.
15 changes: 1 addition & 14 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,13 @@ workflows:
test:
jobs:
- build-all
- test-netcore-linux:
name: .NET Core 3.1 - Linux
docker-image: mcr.microsoft.com/dotnet/core/sdk:3.1-focal
build-target-framework: netcoreapp3.1
test-target-framework: netcoreapp3.1
requires:
- build-all
- test-netcore-linux:
name: .NET 6.0 - Linux
docker-image: mcr.microsoft.com/dotnet/sdk:6.0-focal
build-target-framework: net6.0
test-target-framework: net6.0
requires:
- build-all
- test-windows:
name: .NET Core 3.1 - Windows
build-target-framework: netcoreapp3.1
test-target-framework: netcoreapp3.1
requires:
- build-all
- test-windows:
name: .NET Framework 4.6.2 - Windows
build-target-framework: net462
Expand Down Expand Up @@ -177,4 +164,4 @@ jobs:
# -debug -stop-service-at-end -junit ./circle-reports/contract-tests-junit.xml

- store_test_results:
path: circle-reports
path: circle-reports
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,6 @@ project.lock.json

*.snk
*.p12

# Jetbrains
.idea
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ clean:
dotnet clean

TEMP_TEST_OUTPUT=/tmp/sdk-contract-test-service.log
BUILDFRAMEWORKS ?= netcoreapp3.1
TESTFRAMEWORK ?= netcoreapp3.1
BUILDFRAMEWORKS ?= net6.0
TESTFRAMEWORK ?= net6.0

build-contract-tests:
@cd contract-tests && dotnet build TestService.csproj
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ For using LaunchDarkly in *client-side* .NET applications, including mobile (Xam
This version of the SDK is built for the following targets:

* .NET 6.0: runs on .NET 6.0 and above (including higher major versions).
* .NET Core 3.1: runs on .NET Core 3.1+.
* .NET Framework 4.6.2: runs on .NET Framework 4.6.2 and above.
* .NET Standard 2.0: runs in any project that is targeted to .NET Standard 2.x rather than to a specific runtime platform.

Expand Down
11 changes: 11 additions & 0 deletions contract-tests/CallbackRepresentations.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using LaunchDarkly.Sdk;
using LaunchDarkly.Sdk.Server.Hooks;

namespace TestService
{
Expand All @@ -16,4 +19,12 @@ public class BigSegmentStoreGetMembershipResponse
{
public Dictionary<string, bool?> Values { get; set; }
}

public class EvaluationHookParams
{
public EvaluationSeriesContext EvaluationSeriesContext { get; set; }
public ImmutableDictionary<string, object> EvaluationSeriesData { get; set; }
public EvaluateFlagResponse EvaluationDetail { get; set; }
public string Stage { get; set; }
}
}
17 changes: 17 additions & 0 deletions contract-tests/Representations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class SdkConfigParams
public SdkConfigEventParams Events { get; set; }
public SdkConfigBigSegmentsParams BigSegments { get; set; }
public SdkTagParams Tags { get; set; }
public SdkHookParams Hooks { get; set; }
}

public class SdkTagParams
Expand All @@ -38,6 +39,22 @@ public class SdkTagParams
public string ApplicationVersion { get; set; }
}

public class HookData
{
public Dictionary<string, LdValue> BeforeEvaluation { get; set; }
public Dictionary<string, LdValue> AfterEvaluation { get; set; }
}
public class HookConfig
{
public string Name { get; set; }
public Uri CallbackUri { get; set; }
public HookData Data { get; set; }
}

public class SdkHookParams
{
public List<HookConfig> Hooks { get; set; }
}
public class SdkConfigStreamParams
{
public Uri BaseUri { get; set; }
Expand Down
11 changes: 11 additions & 0 deletions contract-tests/SdkClientEntity.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using LaunchDarkly.Logging;
using LaunchDarkly.Sdk;
using LaunchDarkly.Sdk.Json;
using LaunchDarkly.Sdk.Server;
using LaunchDarkly.Sdk.Server.Hooks;
using LaunchDarkly.Sdk.Server.Migrations;

namespace TestService
Expand Down Expand Up @@ -361,6 +363,15 @@ private static Configuration BuildSdkConfig(SdkConfigParams sdkParams, ILogAdapt
builder.ApplicationInfo(infoBuilder);
}

if (sdkParams.Hooks != null)
{
var hooks = sdkParams.Hooks.Hooks.Select(hook =>
new TestHook(hook.Name, new CallbackService(hook.CallbackUri), hook.Data?.BeforeEvaluation, hook.Data?.AfterEvaluation)
);

builder.Hooks(Components.Hooks(hooks));
}

return builder.Build();
}

Expand Down
65 changes: 65 additions & 0 deletions contract-tests/TestHook.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using LaunchDarkly.Sdk;
using LaunchDarkly.Sdk.Server.Hooks;

namespace TestService
{
using SeriesData = ImmutableDictionary<string, object>;

public class TestHook: Hook
{
private readonly CallbackService _service;
private readonly Dictionary<string, LdValue> _before;
private readonly Dictionary<string, LdValue> _after;

public TestHook(string name, CallbackService service, Dictionary<string, LdValue> before, Dictionary<string, LdValue> after) : base(name)
{
_service = service;
_before = before;
_after = after;
}

public override SeriesData BeforeEvaluation(EvaluationSeriesContext context, SeriesData data)
{
_service.Post("", new EvaluationHookParams()
{
EvaluationSeriesContext = context,
EvaluationSeriesData = data,
Stage = "beforeEvaluation"
});


if (_before == null) return base.BeforeEvaluation(context, data);
var builder = data.ToBuilder();
foreach (var entry in _before)
{
builder[entry.Key] = entry.Value;
}

return builder.ToImmutable();
}

public override SeriesData AfterEvaluation(EvaluationSeriesContext context, SeriesData data, EvaluationDetail<LdValue> detail)
{
_service.Post("", new EvaluationHookParams()
{
EvaluationSeriesContext = context,
EvaluationSeriesData = data,
EvaluationDetail = new EvaluateFlagResponse(){Reason = detail.Reason, VariationIndex = detail.VariationIndex, Value = detail.Value},
Stage = "afterEvaluation"
});


if (_after == null) return base.AfterEvaluation(context, data, detail);
var builder = data.ToBuilder();
foreach (var entry in _after)
{
builder[entry.Key] = entry.Value;
}

return builder.ToImmutable();
}
}
}
3 changes: 2 additions & 1 deletion contract-tests/TestService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ public class Webapp
"event-sampling",
"tags",
"inline-context",
"anonymous-redaction"
"anonymous-redaction",
"evaluation-hooks"
};

public readonly Handler Handler;
Expand Down
38 changes: 38 additions & 0 deletions src/LaunchDarkly.ServerSdk/Components.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using LaunchDarkly.Logging;
using LaunchDarkly.Sdk.Server.Hooks;
using LaunchDarkly.Sdk.Server.Integrations;
using LaunchDarkly.Sdk.Server.Interfaces;
using LaunchDarkly.Sdk.Server.Internal;
Expand Down Expand Up @@ -180,6 +183,41 @@ public static LoggingConfigurationBuilder Logging() =>
public static LoggingConfigurationBuilder Logging(ILogAdapter adapter) =>
new LoggingConfigurationBuilder().Adapter(adapter);

/// <summary>
/// Returns a configuration builder for the SDK's hook configuration.
///
/// <example>
/// <code>
/// var config = Configuration.Builder(sdkKey)
/// .Hooks(Components.Hooks()
/// .Add(new MyHook(...))
/// .Add(new MyOtherHook(...))
/// ).Build();
/// </code>
/// </example>
/// </summary>
/// <returns>a configuration builder</returns>
public static HookConfigurationBuilder Hooks() => new HookConfigurationBuilder();

/// <summary>
/// Returns a configuration builder for the SDK's hook configuration, with an initial set of hooks given
/// as a parameter.
///
/// Use this instead of <see cref="Hooks()"/> if you already have an existing collection of hooks satisfying the
/// IEnumerable interface.
///
/// <example>
/// <code>
/// var listOfHooks = ...;
/// var config = Configuration.Builder(sdkKey)
/// .Hooks(Components.Hooks(listOfHooks)).Build();
/// </code>
/// </example>
/// </summary>
/// <param name="hooks">a collection of hooks</param>
/// <returns>a configuration builder</returns>
public static HookConfigurationBuilder Hooks(IEnumerable<Hook> hooks) => new HookConfigurationBuilder(hooks);

/// <summary>
/// Returns a configuration object that disables analytics events.
/// </summary>
Expand Down
7 changes: 7 additions & 0 deletions src/LaunchDarkly.ServerSdk/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ public class Configuration
/// </summary>
public WrapperInfoBuilder WrapperInfo { get; }


/// <summary>
/// Hooks configuration which contains a list of zero or more hooks to be executed by the SDK at points of interest.
/// </summary>
public HookConfigurationBuilder Hooks { get; }

#endregion

#region Public methods
Expand Down Expand Up @@ -182,6 +188,7 @@ internal Configuration(ConfigurationBuilder builder)
StartWaitTime = builder._startWaitTime;
ApplicationInfo = builder._applicationInfo;
WrapperInfo = builder._wrapperInfo;
Hooks = builder._hooks;
}

#endregion
Expand Down
16 changes: 16 additions & 0 deletions src/LaunchDarkly.ServerSdk/ConfigurationBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using LaunchDarkly.Logging;
using LaunchDarkly.Sdk.Server.Hooks;
using LaunchDarkly.Sdk.Server.Integrations;
using LaunchDarkly.Sdk.Server.Interfaces;
using LaunchDarkly.Sdk.Server.Subsystems;
Expand Down Expand Up @@ -36,6 +38,7 @@ public sealed class ConfigurationBuilder
internal IComponentConfigurer<IDataStore> _dataStore = null;
internal bool _diagnosticOptOut = false;
internal IComponentConfigurer<IEventProcessor> _events = null;
internal HookConfigurationBuilder _hooks = null;
internal IComponentConfigurer<HttpConfiguration> _http = null;
internal IComponentConfigurer<LoggingConfiguration> _logging = null;
internal bool _offline = false;
Expand All @@ -61,6 +64,7 @@ internal ConfigurationBuilder(Configuration copyFrom)
_dataStore = copyFrom.DataStore;
_diagnosticOptOut = copyFrom.DiagnosticOptOut;
_events = copyFrom.Events;
_hooks = copyFrom.Hooks;
_http = copyFrom.Http;
_logging = copyFrom.Logging;
_offline = copyFrom.Offline;
Expand Down Expand Up @@ -275,6 +279,18 @@ public ConfigurationBuilder Logging(IComponentConfigurer<LoggingConfiguration> l
public ConfigurationBuilder Logging(ILogAdapter logAdapter) =>
Logging(Components.Logging(logAdapter));


/// <summary>
/// Configures the SDK's user-defined hooks.
/// </summary>
/// <param name="hooksConfig">the hook configuration</param>
/// <returns>the same builder</returns>
public ConfigurationBuilder Hooks(HookConfigurationBuilder hooksConfig)
{
_hooks = hooksConfig;
return this;
}

/// <summary>
/// Sets whether or not this client is offline. If true, no calls to Launchdarkly will be made.
/// </summary>
Expand Down
42 changes: 42 additions & 0 deletions src/LaunchDarkly.ServerSdk/Hooks/EvaluationSeriesContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace LaunchDarkly.Sdk.Server.Hooks
{
/// <summary>
/// EvaluationSeriesContext represents parameters associated with a feature flag evaluation. It is
/// made available in <see cref="Hook"/> stage callbacks.
/// </summary>
public sealed class EvaluationSeriesContext {
/// <summary>
/// The key of the feature flag.
/// </summary>
public string FlagKey { get; }

/// <summary>
/// The Context used for evaluation.
/// </summary>
public Context Context { get; }

/// <summary>
/// The user-provided default value for the evaluation.
/// </summary>
public LdValue DefaultValue { get; }

/// <summary>
/// The variation method that triggered the evaluation.
/// </summary>
public string Method { get; }

/// <summary>
/// Constructs a new EvaluationSeriesContext.
/// </summary>
/// <param name="flagKey">the flag key</param>
/// <param name="context">the context</param>
/// <param name="defaultValue">the default value</param>
/// <param name="method">the variation method</param>
public EvaluationSeriesContext(string flagKey, Context context, LdValue defaultValue, string method) {
FlagKey = flagKey;
Context = context;
DefaultValue = defaultValue;
Method = method;
}
}
}
Loading

0 comments on commit 7c1c9ab

Please sign in to comment.