diff --git a/.github/workflows/csharp.yml b/.github/workflows/csharp.yml index b55407af..1650a685 100644 --- a/.github/workflows/csharp.yml +++ b/.github/workflows/csharp.yml @@ -75,7 +75,7 @@ jobs: - name: Restore dependencies run: dotnet restore OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj - name: Build & strongly name assemblies - run: dotnet build OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=D:\a\csharp-sdk\csharp-sdk\keypair.snk -c Release + run: dotnet build OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=$(pwd)/keypair.snk -c Release netStandard20: name: Build Standard 2.0 @@ -98,7 +98,7 @@ jobs: - name: Restore dependencies run: dotnet restore OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj - name: Build & strongly name assemblies - run: dotnet build OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=D:\a\csharp-sdk\csharp-sdk\keypair.snk -c Release + run: dotnet build OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj /p:SignAssembly=true /p:AssemblyOriginatorKeyFile=$(pwd)/keypair.snk -c Release integration_tests: name: Run Integration Tests @@ -106,7 +106,6 @@ jobs: uses: optimizely/csharp-sdk/.github/workflows/integration_test.yml@master secrets: CI_USER_TOKEN: ${{ secrets.CI_USER_TOKEN }} - TRAVIS_COM_TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }} fullstack_production_suite: name: Run Performance Tests @@ -116,4 +115,3 @@ jobs: FULLSTACK_TEST_REPO: ProdTesting secrets: CI_USER_TOKEN: ${{ secrets.CI_USER_TOKEN }} - TRAVIS_COM_TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }} diff --git a/.github/workflows/csharp_release.yml b/.github/workflows/csharp_release.yml index cd80b0b2..80461161 100644 --- a/.github/workflows/csharp_release.yml +++ b/.github/workflows/csharp_release.yml @@ -3,6 +3,7 @@ on: release: types: [ published ] # Trigger on published pre-releases and releases + workflow_dispatch: jobs: variables: @@ -33,13 +34,13 @@ jobs: runs-on: windows-2019 # required version for Framework 4.0 steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ needs.variables.outputs.tag }} - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v1 + uses: microsoft/setup-msbuild@v2 - name: Setup NuGet - uses: NuGet/setup-nuget@v1 + uses: nuget/setup-nuget@v2 - name: Restore NuGet packages run: nuget restore ./OptimizelySDK.NETFramework.sln - name: Build and strongly name assemblies @@ -57,11 +58,11 @@ jobs: runs-on: windows-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ needs.variables.outputs.tag }} - name: Setup .NET - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v4 - name: Restore dependencies run: dotnet restore OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj - name: Build and strongly name assemblies @@ -79,11 +80,11 @@ jobs: runs-on: windows-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ needs.variables.outputs.tag }} - name: Setup .NET - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v4 - name: Restore dependencies run: dotnet restore OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj - name: Build and strongly name Standard 2.0 project diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index cf9a96b3..b56cc881 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -9,8 +9,6 @@ on: secrets: CI_USER_TOKEN: required: true - TRAVIS_COM_TOKEN: - required: true jobs: test: runs-on: ubuntu-latest @@ -19,8 +17,8 @@ jobs: with: # You should create a personal access token and store it in your repository token: ${{ secrets.CI_USER_TOKEN }} - repository: 'optimizely/travisci-tools' - path: 'home/runner/travisci-tools' + repository: 'optimizely/ci-helper-tools' + path: 'home/runner/ci-helper-tools' ref: 'master' - name: set SDK Branch if PR env: @@ -28,14 +26,12 @@ jobs: if: ${{ github.event_name == 'pull_request' }} run: | echo "SDK_BRANCH=$HEAD_REF" >> $GITHUB_ENV - echo "TRAVIS_BRANCH=$HEAD_REF" >> $GITHUB_ENV - name: set SDK Branch if not pull request env: REF_NAME: ${{ github.ref_name }} if: ${{ github.event_name != 'pull_request' }} run: | echo "SDK_BRANCH=$REF_NAME" >> $GITHUB_ENV - echo "TRAVIS_BRANCH=$REF_NAME" >> $GITHUB_ENV - name: Trigger build env: SDK: csharp @@ -51,9 +47,8 @@ jobs: PULL_REQUEST_SHA: ${{ github.event.pull_request.head.sha }} PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} UPSTREAM_SHA: ${{ github.sha }} - TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }} EVENT_MESSAGE: ${{ github.event.message }} HOME: 'home/runner' run: | echo "$GITHUB_CONTEXT" - home/runner/travisci-tools/trigger-script-with-status-update.sh + home/runner/ci-helper-tools/trigger-script-with-status-update.sh diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index b60e311d..54ba8165 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -9,21 +9,21 @@ jobs: runs-on: windows-latest steps: - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v4 with: java-version: 1.11 - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Cache SonarCloud packages - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~\sonar\cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - name: Cache SonarCloud scanner id: cache-sonar-scanner - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: .\.sonar\scanner key: ${{ runner.os }}-sonar-scanner diff --git a/OptimizelySDK.DemoApp/Scripts/README.md b/OptimizelySDK.DemoApp/Scripts/README.md index ff1e9551..12510e26 100644 --- a/OptimizelySDK.DemoApp/Scripts/README.md +++ b/OptimizelySDK.DemoApp/Scripts/README.md @@ -7,7 +7,6 @@

- Build Status Stable Release Size bitHound Overall Score Istanbul Code Coverage @@ -34,7 +33,7 @@ to make it possible to position it near a given reference element. The engine is completely modular and most of its features are implemented as **modifiers** (similar to middlewares or plugins). -The whole code base is written in ES2015 and its features are automatically tested on real browsers thanks to [SauceLabs](https://saucelabs.com/) and [TravisCI](https://travis-ci.org/). +The whole code base is written in ES2015 and its features are automatically tested on real browsers thanks to [SauceLabs](https://saucelabs.com/). Popper.js has zero dependencies. No jQuery, no LoDash, nothing. It's used by big companies like [Twitter in Bootstrap v4](https://getbootstrap.com/), [Microsoft in WebClipper](https://github.com/OneNoteDev/WebClipper) and [Atlassian in AtlasKit](https://aui-cdn.atlassian.com/atlaskit/registry/). diff --git a/OptimizelySDK.Tests/OptimizelyTest.cs b/OptimizelySDK.Tests/OptimizelyTest.cs index 5dab3aec..0adb57ec 100644 --- a/OptimizelySDK.Tests/OptimizelyTest.cs +++ b/OptimizelySDK.Tests/OptimizelyTest.cs @@ -339,6 +339,12 @@ public void TestDecisionNotificationSentWhenSendFlagDecisionsFalseAndFeature() { "decisionEventDispatched", true }, + { + "experimentId", "7718750065" + }, + { + "variationId", "7713030086" + } }))), Times.Once); EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Once); @@ -405,6 +411,12 @@ public void TestDecisionNotificationSentWhenSendFlagDecisionsTrueAndFeature() { "decisionEventDispatched", true }, + { + "experimentId", "7718750065" + }, + { + "variationId", "7713030086" + } }))), Times.Once); EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Once); @@ -476,6 +488,12 @@ public void TestDecisionNotificationNotSentWhenSendFlagDecisionsFalseAndRollout( { "decisionEventDispatched", false }, + { + "experimentId", experiment.Id + }, + { + "variationId", variation.Id + } }))), Times.Once); EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Never); @@ -547,6 +565,12 @@ public void TestDecisionNotificationSentWhenSendFlagDecisionsTrueAndRollout() { "decisionEventDispatched", true }, + { + "experimentId", experiment.Id + }, + { + "variationId", variation.Id + } }))), Times.Once); EventDispatcherMock.Verify(dispatcher => dispatcher.DispatchEvent(It.IsAny()), Times.Once); @@ -2361,8 +2385,7 @@ public void Assert.AreEqual(expectedValue, variableValue); LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"Got variable value ""{variableValue}"" for variable ""{variableKey - }"" of feature flag ""{featureKey}"".")); + $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); } [Test] @@ -2406,8 +2429,7 @@ public void Assert.AreEqual(expectedValue, variableValue); LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"Got variable value ""{variableValue}"" for variable ""{variableKey - }"" of feature flag ""{featureKey}"".")); + $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); } [Test] @@ -2439,8 +2461,7 @@ public void Assert.AreEqual(expectedValue, variableValue); LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"Feature ""{featureKey}"" is not enabled for user {TestUserId - }. Returning the default variable value ""{variableValue}"".")); + $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning the default variable value ""{variableValue}"".")); } [Test] @@ -2484,8 +2505,7 @@ public void Assert.AreEqual(expectedValue, variableValue); LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"Feature ""{featureKey}"" is not enabled for user {TestUserId - }. Returning the default variable value ""{variableValue}"".")); + $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning the default variable value ""{variableValue}"".")); } [Test] @@ -2515,8 +2535,7 @@ public void Assert.AreEqual(expectedValue, variableValue); LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"Got variable value ""true"" for variable ""{variableKey}"" of feature flag ""{ - featureKey}"".")); + $@"Got variable value ""true"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); } [Test] @@ -2562,8 +2581,7 @@ public void Assert.AreEqual(expectedStringValue, variableValue.GetValue("string_var")); LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"Got variable value ""{variableValue}"" for variable ""{variableKey - }"" of feature flag ""{featureKey}"".")); + $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); } [Test] @@ -2609,8 +2627,7 @@ public void Assert.AreEqual(expectedStringValue, variableValue.GetValue("string_var")); LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"Got variable value ""{variableValue}"" for variable ""{variableKey - }"" of feature flag ""{featureKey}"".")); + $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); } [Test] @@ -2654,8 +2671,7 @@ public void Assert.AreEqual(expectedValue, variableValue); LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"Got variable value ""{variableValue}"" for variable ""{variableKey - }"" of feature flag ""{featureKey}"".")); + $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); } [Test] @@ -2684,8 +2700,7 @@ public void variableKey, TestUserId, null); Assert.AreEqual(expectedValue, variableValue); LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"Feature ""{featureKey}"" is not enabled for user {TestUserId - }. Returning the default variable value ""true"".")); + $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning the default variable value ""true"".")); } [Test] @@ -2728,8 +2743,7 @@ public void Assert.AreEqual(expectedValue, variableValue); LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"Feature ""{featureKey}"" is not enabled for user {TestUserId - }. Returning the default variable value ""{variableValue}"".")); + $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning the default variable value ""{variableValue}"".")); } [Test] @@ -2758,8 +2772,7 @@ public void Assert.AreEqual(expectedValue, variableValue); LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"User ""{TestUserId}"" is not in any variation for feature flag ""{featureKey - }"", returning default value ""{variableValue}"".")); + $@"User ""{TestUserId}"" is not in any variation for feature flag ""{featureKey}"", returning default value ""{variableValue}"".")); } #endregion Feature Toggle Tests @@ -2822,8 +2835,7 @@ public void TestGetFeatureVariableValueForTypeGivenFeatureKeyOrVariableKeyNotFou LoggerMock.Verify(l => l.Log(LogLevel.ERROR, $@"Feature key ""{featureKey}"" is not in datafile.")); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, - $@"No feature variable was found for key ""{variableKey - }"" in feature flag ""double_single_variable_feature"".")); + $@"No feature variable was found for key ""{variableKey}"" in feature flag ""double_single_variable_feature"".")); } // Should return null and log error message when variable type is invalid. @@ -2851,17 +2863,13 @@ public void TestGetFeatureVariableValueForTypeGivenInvalidVariableType() "string_single_variable_feature", "json_var", TestUserId, null, variableTypeInt)); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, - $@"Variable is of type ""double"", but you requested it as type ""{variableTypeBool - }"".")); + $@"Variable is of type ""double"", but you requested it as type ""{variableTypeBool}"".")); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, - $@"Variable is of type ""boolean"", but you requested it as type ""{ - variableTypeDouble}"".")); + $@"Variable is of type ""boolean"", but you requested it as type ""{variableTypeDouble}"".")); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, - $@"Variable is of type ""integer"", but you requested it as type ""{ - variableTypeString}"".")); + $@"Variable is of type ""integer"", but you requested it as type ""{variableTypeString}"".")); LoggerMock.Verify(l => l.Log(LogLevel.ERROR, - $@"Variable is of type ""string"", but you requested it as type ""{variableTypeInt - }"".")); + $@"Variable is of type ""string"", but you requested it as type ""{variableTypeInt}"".")); } [Test] @@ -2913,8 +2921,7 @@ public void TestGetFeatureVariableValueForTypeGivenFeatureFlagIsNotEnabledForUse Assert.AreEqual(expectedValue, variableValue); LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"Feature ""{featureKey}"" is not enabled for user {TestUserId - }. Returning the default variable value ""{variableValue}"".")); + $@"Feature ""{featureKey}"" is not enabled for user {TestUserId}. Returning the default variable value ""{variableValue}"".")); } // Should return default value and log message when feature is enabled for the user @@ -2954,9 +2961,7 @@ public void Assert.AreEqual(expectedValue, variableValue); LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"Variable ""{variableKey - }"" is not used in variation ""control"", returning default value ""{expectedValue - }"".")); + $@"Variable ""{variableKey}"" is not used in variation ""control"", returning default value ""{expectedValue}"".")); } // Should return variable value from variation and log message when feature is enabled for the user @@ -2994,8 +2999,7 @@ public void Assert.AreEqual(expectedValue, variableValue); LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"Got variable value ""{variableValue}"" for variable ""{variableKey - }"" of feature flag ""{featureKey}"".")); + $@"Got variable value ""{variableValue}"" for variable ""{variableKey}"" of feature flag ""{featureKey}"".")); } // Verify that GetFeatureVariableValueForType returns correct variable value for rollout rule. @@ -3149,8 +3153,7 @@ public void TestIsFeatureEnabledGivenFeatureFlagIsEnabledAndUserIsNotBeingExperi // SendImpressionEvent() does not get called. LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"The user ""{TestUserId}"" is not being experimented on feature ""{featureKey - }""."), Times.Once); + $@"The user ""{TestUserId}"" is not being experimented on feature ""{featureKey}""."), Times.Once); LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Feature flag ""{featureKey}"" is enabled for user ""{TestUserId}"".")); @@ -3183,8 +3186,7 @@ public void TestIsFeatureEnabledGivenFeatureFlagIsEnabledAndUserIsBeingExperimen // SendImpressionEvent() gets called. LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"The user ""{TestUserId}"" is not being experimented on feature ""{featureKey - }""."), Times.Never); + $@"The user ""{TestUserId}"" is not being experimented on feature ""{featureKey}""."), Times.Never); LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Feature flag ""{featureKey}"" is enabled for user ""{TestUserId}"".")); @@ -3218,8 +3220,7 @@ public void TestIsFeatureEnabledGivenFeatureFlagIsNotEnabledAndUserIsBeingExperi // SendImpressionEvent() gets called. LoggerMock.Verify(l => l.Log(LogLevel.INFO, - $@"The user ""{TestUserId}"" is not being experimented on feature ""{featureKey - }""."), Times.Never); + $@"The user ""{TestUserId}"" is not being experimented on feature ""{featureKey}""."), Times.Never); LoggerMock.Verify(l => l.Log(LogLevel.INFO, $@"Feature flag ""{featureKey}"" is not enabled for user ""{TestUserId}"".")); diff --git a/OptimizelySDK.Tests/OptimizelyUserContextTest.cs b/OptimizelySDK.Tests/OptimizelyUserContextTest.cs index 76d0d8b8..21ae10db 100644 --- a/OptimizelySDK.Tests/OptimizelyUserContextTest.cs +++ b/OptimizelySDK.Tests/OptimizelyUserContextTest.cs @@ -1081,6 +1081,12 @@ public void TestDecisionNotification() { "decisionEventDispatched", true }, + { + "experimentId", "122235" + }, + { + "variationId", "122236" + }, }; var userAttributes = new UserAttributes diff --git a/OptimizelySDK/Optimizely.cs b/OptimizelySDK/Optimizely.cs index b1766985..99cbdaaf 100644 --- a/OptimizelySDK/Optimizely.cs +++ b/OptimizelySDK/Optimizely.cs @@ -1000,7 +1000,8 @@ ProjectConfig projectConfig ) { var userId = user.GetUserId(); - + string experimentId = null; + string variationId = null; var flagEnabled = false; if (flagDecision.Variation != null) { @@ -1008,8 +1009,12 @@ ProjectConfig projectConfig { flagEnabled = true; } + variationId = flagDecision.Variation.Id; + } + if (flagDecision.Experiment != null) + { + experimentId = flagDecision.Experiment.Id; } - Logger.Log(LogLevel.INFO, $"Feature \"{flagKey}\" is enabled for user \"{userId}\"? {flagEnabled}"); @@ -1062,6 +1067,8 @@ ProjectConfig projectConfig { "ruleKey", ruleKey }, { "reasons", reasonsToReport }, { "decisionEventDispatched", decisionEventDispatched }, + { "experimentId", experimentId }, + { "variationId", variationId }, }; NotificationCenter.SendNotifications(NotificationCenter.NotificationType.Decision, diff --git a/README.md b/README.md index f6b04d5c..9de4ddc7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Optimizely C# SDK ![Semantic](https://img.shields.io/badge/sem-ver-lightgrey.svg?style=plastic) -[![Build Status](https://travis-ci.org/optimizely/csharp-sdk.svg?branch=master)](https://travis-ci.org/optimizely/csharp-sdk) +![CI](https://github.com/optimizely/csharp-sdk/actions/workflows/csharp.yml/badge.svg?branch=master) [![NuGet](https://img.shields.io/nuget/v/Optimizely.SDK.svg?style=plastic)](https://www.nuget.org/packages/Optimizely.SDK/) [![Apache 2.0](https://img.shields.io/github/license/nebula-plugins/gradle-extra-configurations-plugin.svg)](http://www.apache.org/licenses/LICENSE-2.0)