Skip to content

Commit

Permalink
[featureflag]: use float to check for flag probability (#1237)
Browse files Browse the repository at this point in the history
* use float to check for flag probability

Signed-off-by: Pierre Tessier <pierre@pierretessier.com>

* use float to check for flag probability

Signed-off-by: Pierre Tessier <pierre@pierretessier.com>

* Update CHANGELOG.md

* Add help text for float feature flags; Make upgrades possible for postgres

* add description for feature flag value

Signed-off-by: Pierre Tessier <pierre@pierretessier.com>

* clean up help text

Signed-off-by: Pierre Tessier <pierre@pierretessier.com>

* return to single migration with decimal type

* use float instead of decimal

Signed-off-by: Pierre Tessier <pierre@pierretessier.com>

---------

Signed-off-by: Pierre Tessier <pierre@pierretessier.com>
Co-authored-by: Austin Parker <austin@ap2.io>
Co-authored-by: Josh Lee <josh@joshuamlee.com>
Co-authored-by: Juliano Costa <julianocosta89@outlook.com>
  • Loading branch information
4 people committed Dec 14, 2023
1 parent c8c192a commit 430b4c9
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 47 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ release.

* update PHP quoteservice to use 1.0.0
([#1236](https://github.com/open-telemetry/opentelemetry-demo/pull/1236))
* Add ability to do probabilistic A/B testing with feature flags
([#1237](https://github.com/open-telemetry/opentelemetry-demo/pull/1237))
* add env var for pinning trace-based test tool version
([#1239](https://github.com/open-telemetry/opentelemetry-demo/pull/1239))
* [cartservice] Add .NET memory, CPU, and thread metrics
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0


defmodule Featureflagservice.FeatureFlags.FeatureFlag do
use Ecto.Schema
import Ecto.Changeset

schema "featureflags" do
field :description, :string
field :enabled, :boolean, default: false
field :enabled, :float, default: 0.0
field :name, :string

timestamps()
Expand All @@ -19,6 +18,7 @@ defmodule Featureflagservice.FeatureFlags.FeatureFlag do
feature_flag
|> cast(attrs, [:name, :description, :enabled])
|> validate_required([:name, :description, :enabled])
|> validate_number(:enabled, greater_than_or_equal_to: 0.0, less_than_or_equal_to: 1.0)
|> unique_constraint(:name)
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,15 @@
<%= text_input f, :description %>
<%= error_tag f, :description %>

<div class="float-right">
<%= checkbox f, :enabled %>
<%= label f, :enabled, class: "label-inline" %>
</div>
<%= label f, :enabled %>
<%= number_input f, :enabled, min: 0, max: 1, step: 0.01, "aria-describedby": "enabled_help_text" %>
<p id="enabled_help_text" style="font-size: smaller; margin-top: -10px;">
A decimal value between 0 and 1 (inclusive)<br />
0.0 is always disabled<br />
1.0 is always enabled<br />
All values between set a percentage chance on each request<br />
example: 0.55 is enabled 55% of the time<br />
</p>
<%= error_tag f, :enabled %>

<%= submit "Save" %>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@


defmodule Featureflagservice.Repo.Migrations.CreateFeatureflags do
use Ecto.Migration

def change do
create table(:featureflags) do
add :name, :string
add :description, :string
add :enabled, :boolean, default: false, null: false
add :enabled, :float, default: 0.0, null: false

timestamps()
end
Expand All @@ -21,22 +19,26 @@ defmodule Featureflagservice.Repo.Migrations.CreateFeatureflags do
repo().insert(%Featureflagservice.FeatureFlags.FeatureFlag{
name: "productCatalogFailure",
description: "Fail product catalog service on a specific product",
enabled: false})
enabled: 0.0
})

repo().insert(%Featureflagservice.FeatureFlags.FeatureFlag{
name: "recommendationCache",
description: "Cache recommendations",
enabled: false})
enabled: 0.0
})

repo().insert(%Featureflagservice.FeatureFlags.FeatureFlag{
name: "adServiceFailure",
description: "Fail ad service requests sporadically",
enabled: false})
enabled: 0.0
})

repo().insert(%Featureflagservice.FeatureFlags.FeatureFlag{
name: "cartServiceFailure",
description: "Fail cart service requests sporadically",
enabled: false})
enabled: 0.0
})
end

defp execute_down do
Expand Down
76 changes: 42 additions & 34 deletions src/featureflagservice/src/ffs_service.erl
Original file line number Diff line number Diff line change
Expand Up @@ -17,56 +17,64 @@
-behaviour(ffs_service_bhvr).

-export([get_flag/2,
create_flag/2,
update_flag/2,
list_flags/2,
delete_flag/2]).
create_flag/2,
update_flag/2,
list_flags/2,
delete_flag/2]).

-include_lib("grpcbox/include/grpcbox.hrl").

-include_lib("opentelemetry_api/include/otel_tracer.hrl").

-spec get_flag(ctx:t(), ffs_demo_pb:get_flag_request()) ->
{ok, ffs_demo_pb:get_flag_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
{ok, ffs_demo_pb:get_flag_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
get_flag(Ctx, #{name := Name}) ->
case 'Elixir.Featureflagservice.FeatureFlags':get_feature_flag_by_name(Name) of
nil ->
{grpc_error, {?GRPC_STATUS_NOT_FOUND, <<"the requested feature flag does not exist">>}};
#{'__struct__' := 'Elixir.Featureflagservice.FeatureFlags.FeatureFlag',
description := Description,
enabled := Enabled,
inserted_at := CreatedAt,
updated_at := UpdatedAt
} ->
?set_attribute('app.featureflag.name', Name),
?set_attribute('app.featureflag.enabled', Enabled),
{ok, Epoch} = 'Elixir.NaiveDateTime':from_erl({{1970, 1, 1}, {0, 0, 0}}),
CreatedAtSeconds = 'Elixir.NaiveDateTime':diff(CreatedAt, Epoch),
UpdatedAtSeconds = 'Elixir.NaiveDateTime':diff(UpdatedAt, Epoch),
Flag = #{name => Name,
description => Description,
enabled => Enabled,
created_at => #{seconds => CreatedAtSeconds, nanos => 0},
updated_at => #{seconds => UpdatedAtSeconds, nanos => 0}},
{ok, #{flag => Flag}, Ctx}
end.
case 'Elixir.Featureflagservice.FeatureFlags':get_feature_flag_by_name(Name) of
nil ->
{grpc_error, {?GRPC_STATUS_NOT_FOUND, <<"the requested feature flag does not exist">>}};
#{'__struct__' := 'Elixir.Featureflagservice.FeatureFlags.FeatureFlag',
description := Description,
enabled := Enabled,
inserted_at := CreatedAt,
updated_at := UpdatedAt
} ->
RandomNumber = rand:uniform(100), % Generate a random number between 0 and 100
Probability = trunc(Enabled * 100), % Convert the Enabled value to a percentage
FlagEnabledValue = RandomNumber =< Probability, % Determine if the random number falls within the probability range

?set_attribute('app.featureflag.name', Name),
?set_attribute('app.featureflag.raw_value', Enabled),
?set_attribute('app.featureflag.enabled', FlagEnabledValue),

{ok, Epoch} = 'Elixir.NaiveDateTime':from_erl({{1970, 1, 1}, {0, 0, 0}}),
CreatedAtSeconds = 'Elixir.NaiveDateTime':diff(CreatedAt, Epoch),
UpdatedAtSeconds = 'Elixir.NaiveDateTime':diff(UpdatedAt, Epoch),

Flag = #{name => Name,
description => Description,
enabled => FlagEnabledValue,
created_at => #{seconds => CreatedAtSeconds, nanos => 0},
updated_at => #{seconds => UpdatedAtSeconds, nanos => 0}},

{ok, #{flag => Flag}, Ctx}
end.

-spec create_flag(ctx:t(), ffs_demo_pb:create_flag_request()) ->
{ok, ffs_demo_pb:create_flag_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
{ok, ffs_demo_pb:create_flag_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
create_flag(_Ctx, _) ->
{grpc_error, {?GRPC_STATUS_UNIMPLEMENTED, <<"use the web interface to create flags.">>}}.
{grpc_error, {?GRPC_STATUS_UNIMPLEMENTED, <<"use the web interface to create flags.">>}}.

-spec update_flag(ctx:t(), ffs_demo_pb:update_flag_request()) ->
{ok, ffs_demo_pb:update_flag_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
{ok, ffs_demo_pb:update_flag_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
update_flag(_Ctx, _) ->
{grpc_error, {?GRPC_STATUS_UNIMPLEMENTED, <<"use the web interface to update flags.">>}}.
{grpc_error, {?GRPC_STATUS_UNIMPLEMENTED, <<"use the web interface to update flags.">>}}.

-spec list_flags(ctx:t(), ffs_demo_pb:list_flags_request()) ->
{ok, ffs_demo_pb:list_flags_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
{ok, ffs_demo_pb:list_flags_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
list_flags(_Ctx, _) ->
{grpc_error, {?GRPC_STATUS_UNIMPLEMENTED, <<"use the web interface to view all flags.">>}}.
{grpc_error, {?GRPC_STATUS_UNIMPLEMENTED, <<"use the web interface to view all flags.">>}}.

-spec delete_flag(ctx:t(), ffs_demo_pb:delete_flag_request()) ->
{ok, ffs_demo_pb:delete_flag_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
{ok, ffs_demo_pb:delete_flag_response(), ctx:t()} | grpcbox_stream:grpc_error_response().
delete_flag(_Ctx, _) ->
{grpc_error, {?GRPC_STATUS_UNIMPLEMENTED, <<"use the web interface to delete flags.">>}}.
{grpc_error, {?GRPC_STATUS_UNIMPLEMENTED, <<"use the web interface to delete flags.">>}}.

0 comments on commit 430b4c9

Please sign in to comment.