Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[submodule "Flagsmith.EngineTest/EngineTestData"]
path = Flagsmith.EngineTest/EngineTestData
url = [email protected]:Flagsmith/engine-test-data.git
tag = v1.0.0
tag = v3.4.2
2 changes: 1 addition & 1 deletion Flagsmith.Client.Test/FlagsmithTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public async Task TestGetEnvironmentFlagsUsesLocalEnvironmentWhenAvailable()
var flags = (await flagsmithClientTest.GetEnvironmentFlags()).AllFlags();
var fs = Fixtures.Environment.FeatureStates[0];
Assert.Equal(fs.Enabled, flags[0].Enabled);
Assert.Equal(fs.GetValue(), flags[0].Value);
Assert.Equal(fs.Value, flags[0].Value);
Assert.Equal(fs.Feature.Name, flags[0].GetFeatureName());
mockHttpClient.VerifyHttpRequest(HttpMethod.Get, "/api/v1/environment-document/", Times.Once);
}
Expand Down
68 changes: 0 additions & 68 deletions Flagsmith.Engine/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,73 +43,5 @@ private EvaluationContext<SegmentMetadataT, FeatureMetadataT> GetEnrichedEvaluat
}
return context;
}

/// <summary>
/// Get a list of feature states for a given environment
/// </summary>
/// <param name="environmentModel">the environment model object</param>
/// <returns>list of feature-state model</returns>
public List<FeatureStateModel> GetEnvironmentFeatureStates(EnvironmentModel environmentModel) =>
environmentModel.Project.HideDisabledFlags ? environmentModel.FeatureStates.Where(fs => fs.Enabled).ToList() : environmentModel.FeatureStates;
/// <summary>
/// Get a specific feature state for a given feature_name in a given environment
/// </summary>
/// <param name="environmentModel">environment model object</param>
/// <param name="featureName">name of a feature to get</param>
/// <returns>feature-state model</returns>
public FeatureStateModel GetEnvironmentFeatureState(EnvironmentModel environmentModel, string featureName)
{
var featureState = environmentModel.FeatureStates.FirstOrDefault(fs => fs.Feature.Name == featureName);
if (featureState != null)
return featureState;

throw new FeatureStateNotFound();
}
public List<FeatureStateModel> GetIdentityFeatureStates(EnvironmentModel environmentModel, IdentityModel identity, List<TraitModel> overrideTraits)
{
var featureStates = GetIdentityFeatureStatesMapping(environmentModel, identity, overrideTraits).Values.ToList();

if (environmentModel.Project.HideDisabledFlags)
return featureStates.Where(fs => fs.Enabled).ToList();

return featureStates;
}
public FeatureStateModel GetIdentityFeatureState(EnvironmentModel environmentModel, IdentityModel identity, string featureName, List<TraitModel> overrideTraits)
{
var featureStates = GetIdentityFeatureStatesMapping(environmentModel, identity, overrideTraits);
var matchingFeature = featureStates.FirstOrDefault(x => x.Key.Name == featureName);

if (!matchingFeature.Equals(default(KeyValuePair<FeatureModel, FeatureStateModel>)))
return matchingFeature.Value;

throw new FeatureStateNotFound();
}

public Dictionary<FeatureModel, FeatureStateModel> GetIdentityFeatureStatesMapping(EnvironmentModel environmentModel, IdentityModel identity, List<TraitModel> overrideTraits)
{
var featureStates = environmentModel.FeatureStates.ToDictionary(key => key.Feature, val => val);
var identitySegments = Evaluator.GetIdentitySegments(environmentModel, identity, overrideTraits);
foreach (var matchingSegment in identitySegments)
{
foreach (var featureState in matchingSegment.FeatureStates)
{
FeatureModel feature = featureState.Feature;
var existing = featureStates.FirstOrDefault(x => x.Key.Id == feature.Id);
if (!existing.Equals(default) && existing.Value.IsHigherPriority(featureState))
{
continue;
}

featureStates[feature] = featureState;
}
;
}
identity.IdentityFeatures?.ForEach(x =>
{
if (featureStates.ContainsKey(x.Feature))
featureStates[x.Feature] = x;
});
return featureStates;
}
}
}
43 changes: 2 additions & 41 deletions Flagsmith.Engine/Feature/Models/FeatureStateModel.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
using FlagsmithEngine.Utils;
using System.Linq;
using System.Runtime.Serialization;
using FlagsmithEngine.Exceptions;
Expand All @@ -11,8 +8,6 @@ namespace FlagsmithEngine.Feature.Models
{
public class FeatureStateModel
{
public Hashing Hashing = new Hashing();

[JsonProperty(PropertyName = "feature")]
public FeatureModel Feature { get; set; }
[JsonProperty(PropertyName = "enabled")]
Expand All @@ -23,45 +18,11 @@ public class FeatureStateModel
public List<MultivariateFeatureStateValueModel> MultivariateFeatureStateValues { get; set; }
[JsonProperty(PropertyName = "django_id")]
public int? DjangoId { get; set; }
public string FeatureStateUUID { get; set; } = new Guid().ToString();
[JsonProperty(PropertyName = "featurestate_uuid")]
public string FeatureStateUUID { get; set; }
[JsonProperty(PropertyName = "feature_segment")]
public FeatureSegmentModel FeatureSegment { get; set; } = null;
public object GetValue(string identityId = null) =>
identityId != null && MultivariateFeatureStateValues?.Count > 0 ? GetMultivariateValue(identityId.ToString()) : Value;

public object GetMultivariateValue(string identityId)
{
var percentageValue = Hashing.GetHashedPercentageForObjectIds(new List<string>
{
DjangoId != null ? DjangoId.ToString() : FeatureStateUUID,
identityId.ToString()
});
var startPercentage = 0.0;
foreach (var myValue in MultivariateFeatureStateValues.OrderBy(m => m.Id))
{
var limit = myValue.PercentageAllocation + startPercentage;
if (startPercentage <= percentageValue && percentageValue < limit)
return myValue.MultivariateFeatureOption.Value;
startPercentage = limit;
}
return Value;
}

/// <summary>
/// Another FeatureStateModel is deemed to be higher priority if and only if
/// it has a FeatureSegment and either this.FeatureSegment is null or the
/// value of other.FeatureSegment.priority is lower than that of
/// this.FeatureSegment.priority.
/// </summary>
public bool IsHigherPriority(FeatureStateModel other)
{
if (this.FeatureSegment == null || other.FeatureSegment == null)
{
return this.FeatureSegment != null && other.FeatureSegment == null;
}

return this.FeatureSegment.Priority < other.FeatureSegment.Priority;
}
[OnSerialized()]
private void ValidatePercentageAllocations(StreamingContext _)
{
Expand Down
28 changes: 0 additions & 28 deletions Flagsmith.Engine/Identity/Models/IdentityModel.cs
Original file line number Diff line number Diff line change
@@ -1,43 +1,15 @@
using FlagsmithEngine.Trait.Models;
using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
using FlagsmithEngine.Feature.Models;
using System.Linq;

namespace FlagsmithEngine.Identity.Models
{
public class IdentityModel
{
[JsonProperty("identity_uuid")]
public string IdentityUUID { get; set; } = Guid.NewGuid().ToString();
[JsonProperty("identifier")]
public string Identifier { get; set; }
[JsonProperty("environment_api_key")]
public string EnvironmentApiKey { get; set; }
[JsonProperty("created_date")]
public DateTime CreatedDate { get; set; }
[JsonProperty("identity_traits")]
public List<TraitModel> IdentityTraits { get; set; }
[JsonProperty("identity_features")]
public IdentityFeaturesList IdentityFeatures { get; set; }
[JsonProperty("django_id")]
public int? DjangoId { get; set; }
public string CompositeKey => GenerateCompositeKey(EnvironmentApiKey, Identifier);

public string GenerateCompositeKey(string envKey, string identifier) => $"{envKey}_{identifier}";
public void UpdateTraits(List<TraitModel> traits)
{
var existingModels = IdentityTraits?.Count > 0 ? IdentityTraits.ToDictionary(x => x.TraitKey) : new Dictionary<string, TraitModel>();
traits.ForEach(trait =>
{
if (trait.TraitValue is null)
existingModels.Remove(trait.TraitKey);
else
existingModels[trait.TraitKey] = trait;
});
IdentityTraits = existingModels.Values.ToList();
}
}
}
5 changes: 0 additions & 5 deletions Flagsmith.Engine/Interfaces/IEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,5 @@ namespace FlagsmithEngine.Interfaces
public interface IEngine
{
EvaluationResult<SegmentMetadataT, FeatureMetadataT> GetEvaluationResult<SegmentMetadataT, FeatureMetadataT>(EvaluationContext<SegmentMetadataT, FeatureMetadataT> context);
List<FeatureStateModel> GetEnvironmentFeatureStates(EnvironmentModel environmentModel);
FeatureStateModel GetEnvironmentFeatureState(EnvironmentModel environmentModel, string featureName);
List<FeatureStateModel> GetIdentityFeatureStates(EnvironmentModel environmentModel, IdentityModel identity, List<TraitModel> overrideTraits = null);
FeatureStateModel GetIdentityFeatureState(EnvironmentModel environmentModel, IdentityModel identity, string featureName, List<TraitModel> overrideTraits);
Dictionary<FeatureModel, FeatureStateModel> GetIdentityFeatureStatesMapping(EnvironmentModel environmentModel, IdentityModel identity, List<TraitModel> overrideTraits);
}
}
31 changes: 0 additions & 31 deletions Flagsmith.Engine/Response/Response.cs

This file was deleted.

Loading
Loading