V13-FE-011: finalize search list layout slice
This commit is contained in:
+45
@@ -0,0 +1,45 @@
|
||||
using Dapper;
|
||||
using Kbx.Shared.Experiments.Generated;
|
||||
using Npgsql;
|
||||
|
||||
namespace Kbx.Shared.Experiments;
|
||||
|
||||
public sealed class KbxExperimentAssignmentService(NpgsqlDataSource dataSource)
|
||||
{
|
||||
public async Task<KbxExperimentAssignmentsResponse> EvaluateAsync(Guid tenantId,Guid userId,string screenId,CancellationToken ct)
|
||||
{
|
||||
var definitions=KbxExperimentCatalog.Experiments.Values.Where(x=>x.ScreenId==screenId).ToArray();
|
||||
var items=new List<KbxExperimentAssignmentDto>();
|
||||
await using var connection=await dataSource.OpenConnectionAsync(ct);
|
||||
foreach(var definition in definitions)
|
||||
{
|
||||
var runtime=await connection.QuerySingleOrDefaultAsync<RuntimeRow>(new CommandDefinition("""
|
||||
select state State,rollout_percent RolloutPercent,kill_switch KillSwitch
|
||||
from kbx.experiment_runtime where tenant_id=@TenantId and experiment_id=@ExperimentId;
|
||||
""",new{TenantId=tenantId,ExperimentId=definition.Id},cancellationToken:ct));
|
||||
var state=runtime?.State ?? definition.State;
|
||||
var rollout=runtime?.RolloutPercent ?? definition.RolloutPercent;
|
||||
var killed=runtime?.KillSwitch ?? false;
|
||||
// Regular users only receive actively enrolled assignments. Draft/paused/rolled-back definitions stay internal.
|
||||
if(killed||state!="running"||rollout<=0)continue;
|
||||
// Rollout percentage remains authoritative even for previously assigned users.
|
||||
// Assignment is preserved for reproducibility and becomes active again if rollout is raised later.
|
||||
if(!KbxExperimentAssignmentPolicy.IsEnrolled(tenantId,userId,definition.Id,rollout))continue;
|
||||
var existing=await connection.QuerySingleOrDefaultAsync<string>(new CommandDefinition("""
|
||||
select variant from kbx.experiment_assignments where tenant_id=@TenantId and experiment_id=@ExperimentId and user_id=@UserId;
|
||||
""",new{TenantId=tenantId,ExperimentId=definition.Id,UserId=userId},cancellationToken:ct));
|
||||
if(existing is not null){items.Add(new(definition.Id,definition.FlagId,screenId,true,existing,state));continue;}
|
||||
var variant=KbxExperimentAssignmentPolicy.ChooseVariant(tenantId,userId,definition);
|
||||
await connection.ExecuteAsync(new CommandDefinition("""
|
||||
insert into kbx.experiment_assignments(tenant_id,experiment_id,user_id,variant)
|
||||
values(@TenantId,@ExperimentId,@UserId,@Variant) on conflict do nothing;
|
||||
""",new{TenantId=tenantId,ExperimentId=definition.Id,UserId=userId,Variant=variant},cancellationToken:ct));
|
||||
existing=await connection.QuerySingleAsync<string>(new CommandDefinition("""
|
||||
select variant from kbx.experiment_assignments where tenant_id=@TenantId and experiment_id=@ExperimentId and user_id=@UserId;
|
||||
""",new{TenantId=tenantId,ExperimentId=definition.Id,UserId=userId},cancellationToken:ct));
|
||||
items.Add(new(definition.Id,definition.FlagId,screenId,true,existing,state));
|
||||
}
|
||||
return new(items);
|
||||
}
|
||||
private sealed record RuntimeRow(string State,int RolloutPercent,bool KillSwitch);
|
||||
}
|
||||
Reference in New Issue
Block a user