Code Monkey home page Code Monkey logo

fun_with_flags's People

Contributors

asummers avatar bobbymcwho avatar cadamsraven avatar chubarovnick avatar connorlay avatar coryodaniel avatar duldr avatar eamontaaffe avatar iamvery avatar iurimadeira avatar jc00ke avatar kelvinst avatar kianmeng avatar lostkobrakai avatar lukeledet avatar mariusbutuc avatar mbuffa avatar paulostazeski avatar rodrigues avatar seangeo avatar skylerparr avatar steffende avatar stewart avatar tompave avatar tylerbarker avatar up2jj avatar whatyouhide avatar zaid avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fun_with_flags's Issues

Allow different persistence adapters per env

Currently, if you specify :redis config options in your main config, there is no way to overwrite this config in a different environment. For example, when writing a test config and overriding the adapter defined by :fun_with_flags, :persistence, the adapter gets overwritten but it still attempts to make a connection to a Redis instance as there is still the :fun_with_flags, :redis config that is defined on the main config file but not in say a test.exs config.

ie
config.exs

  config :fun_with_flags, :redis,
    host: <host>
    port: <port>

test.exs

config :fun_with_flags, :cache_bust_notifications, enabled: false

config :fun_with_flags, :persistence, adapter: MyApp.CustomFlagStore

Merged config would be the following:

#Still attempts to connect to redis as this is defined even though a non redis adapter is defined below
  config :fun_with_flags, :redis, 
    host: <host>
    port: <port>

config :fun_with_flags, :cache_bust_notifications, enabled: false

config :fun_with_flags, :persistence, adapter: MyApp.CustomFlagStore

Specifying a customer persistence adapter should not attempt to make a connection to ecto or redis

(UndefinedFunctionError) function Ecto.Query.from/2 is undefined or private

Hey guys, Im trying to use Elixir 1.13 with this package, but Im getting the following error:

==> fun_with_flags
Compiling 21 files (.ex)

== Compilation error in file lib/fun_with_flags/store/persistent/ecto.ex ==
** (UndefinedFunctionError) function Ecto.Query.from/2 is undefined or private. However there is a macro with the same name and arity. Be sure to require Ecto.Query if you intend to invoke this macro
    (ecto 3.7.1) expanding macro: Ecto.Query.from/2
    lib/fun_with_flags/store/persistent/ecto.ex:29: FunWithFlags.Store.Persistent.Ecto.get/1
could not compile dependency :fun_with_flags, "mix compile" failed. Errors may have been logged above. You can recompile this dependency with "mix deps.compile fun_with_flags", update it with "mix deps.update fun_with_flags" or clean it with "mix deps.clean fun_with_flags"

These are the versions Im running:

Erlang/OTP 24 [erts-12.1.5] [source] [64-bit] [smp:6:6] [ds:6:6:10] [async-threads:1] [jit]
Elixir 1.13.0 (compiled with Erlang/OTP 24)
fun_with_flags: 1.8.0
Ecto: 3.7.1
phoenix: 1.6.4

Any clue?

Thanks!

Changing `ecto_table_name` config requires recompile

The ecto_table_name config is evaluated at compile time here: https://github.com/tompave/fun_with_flags/blob/master/lib/fun_with_flags/store/persistent/ecto/record.ex#L11

However this uses Application.get_env in Config.ecto_table_name by way of Config.persistence_config.

The result is that changing the :fun_with_flags, :persistence, :ecto_table_name config has no effect unless FunWithFlags is recompiled.

I would expect FunWithFlags to reflect the config changes, as it does with the rest of the persistence settings.

I found some info here that shows how to set a table name at runtime for an existing schema (with Ecto.put_meta/2).

If this sounds right, I could put together a PR.

[Question] Ensuring feature flags are in sync across dev environments

Hi,

Do you have any suggestions for ensuring that everyone's development / test environments are in sync when folks add new feature flags? We'd love some form of a version controlled config file that at the very least indicates the presence of flags, and even better, the desired flag state / gate / actor, so that each developer doesn't have to know what flags to set in their DB.

Cheers,
Oliver

Compilation failure with elixir-1.13.0-rc.0

fun_with_flags 1.6.0 fails to compile under elixir-1.13.0-rc.0, with the following error:

$ mix deps.compile fun_with_flags
==> fun_with_flags
Compiling 7 files (.ex)

== Compilation error in file lib/fun_with_flags/protocols/actor.ex ==
** (CompileError) lib/fun_with_flags/protocols/actor.ex:116: undefined function defdelegate/2 (there is no such import)

Per the Elixir 1.13.0 release notes:

[Protocol] Add defdelegate to the list of unallowed macros inside protocols as protocols do not allow function definitions

Non atom group names

Is there a reason why group names must be atoms?

We are trying to release a feature to some users via a "bucket" method. Something like "users_bucket:1", where all the users where rem(id, 10) == 1 would have access to the feature. But with only atom groups, implementing this is kinda cumbersome. Also, kinda unsafe, as a lot of dynamic atoms would be created, and we all know how the BEAM feels about having atoms dynamically generated πŸ˜…

Exploring the codebase, I couldn't find anything that explains the validation. Did I miss any points? If not, can I submit a PR removing the need for atom group names? I can come up with something to maintain backwards compatibility ^^

Web UI

Any plans on adding a simple Web UI to this project?

I would suggest creating the web UI as part of this project for ease of setup.

Compiler Warning that Repo is unavailable

Hi and thanks for your work on this library.

I get compiler warnings when I first compile the code:

==> fun_with_flags
Compiling 20 files (.ex)
warning: function Demo.Repo.all/1 is undefined (module Demo.Repo is not available)
Found at 3 locations:
  lib/fun_with_flags/store/persistent/ecto.ex:23
  lib/fun_with_flags/store/persistent/ecto.ex:157
  lib/fun_with_flags/store/persistent/ecto.ex:166

warning: function Demo.Repo.delete_all/1 is undefined (module Demo.Repo is not available)
Found at 3 locations:
  lib/fun_with_flags/store/persistent/ecto.ex:92
  lib/fun_with_flags/store/persistent/ecto.ex:119
  lib/fun_with_flags/store/persistent/ecto.ex:144

warning: function Demo.Repo.insert/2 is undefined (module Demo.Repo is not available)
  lib/fun_with_flags/store/persistent/ecto.ex:193

warning: function Demo.Repo.one/1 is undefined (module Demo.Repo is not available)
  lib/fun_with_flags/store/persistent/ecto.ex:44

warning: function Demo.Repo.transaction/1 is undefined (module Demo.Repo is not available)
  lib/fun_with_flags/store/persistent/ecto.ex:42

warning: function Demo.Repo.update/2 is undefined (module Demo.Repo is not available)
  lib/fun_with_flags/store/persistent/ecto.ex:200

because it looks like this module attribute gets compiled before my app is actually ready (since it gets compiled after fun_with_flags):

Outside of the warning, it doesn't appear to cause any issues but I thought I would bring it up in case it was unknown.

Notifications need to start with the application supervisor

The Notification module has to wait on the Application that contains the PubSub to start otherwise it fails, now this is handled with retries but they break really easily and littler tests.

I know that the notifications are now optional but I think the right approach is just to have the Application supervisor that spawns the PubSub also spawn the the Notification module.

Here are the errors:

FunWithFlags: Cannot subscribe to Phoenix.PubSub process MyApp.PubSub (exception: %ArgumentError{message: "argument error"})
FunWithFlags: retrying to subscribe to Phoenix.PubSub, attempt 2

[Suggestion] Gauging interest for allowing more datatypes within a flag

We have a case where we'd like the ability to toggle a numeric range as a flag. While this isn't purely a feature flag, it has overlap with FWF in the sense that we need a UI to edit values at runtime. A hypothetical API could look like this:

FunWithFlags.enable(:high_quality_threshold, values: %{
  enabled_value: 10..25,
  disabled_value: 0..0
})

FunWithFlags.enabled?(:high_quality_threshold) # true
FunWithFlags.value(:high_quality_threshold) # 10..25

FunWithFlags.disable(:high_quality_threshold)
FunWithFlags.enabled?(:high_quality_threshold) # false
FunWithFlags.value(:high_quality_threshold) # 0..0

This hasn't been fully thought through with regards to how this would interact with actors, gates etc. Furthermore, any type aside from booleans, strings, ints, and nil may be out of scope for an initial attempt.


We're likely going to be building this functionality on top of FWF for our own purposes, but I'd like to know if there would be interest in a PR for this feature with the intent of getting it in the base library/UI library. If so, I'd also like to hear any thoughts you may have on API design.

Support Phoenix PubSub 2.0

Phoenix 1.5.0-rc0 is now out with Phoenix PubSub 2.0, however fun_with_flags currently depends on phoenix_pubsub "~> 1.0".

config is not applied

Hi,

I'm setting this up in a Phoenix application. Like this:

config :fun_with_flags, :cache,
  enabled: true,
  ttl: 900

config :fun_with_flags, :persistence,
  adapter: FunWithFlags.Store.Persistent.Ecto,
  repo: Plus.Repo,
  ecto_table_name: "fun_with_flags_toggles"

config :fun_with_flags, :cache_bust_notifications,
  enabled: true,
  adapter: FunWithFlags.Notifications.PhoenixPubSub,
  client: Plus.PubSub

Migration is setup, ran and I checked that the table is there.
However when I use the API, the error looks like the repo is not configured:

iex β–Ά FunWithFlags.enabled?(:my_feat)

β–Άβ–Άβ–Ά
** (RuntimeError) The NullEctoRepo doesn't implement this. You must configure a proper repo to persist flags with Ecto.
    (fun_with_flags 1.5.1) lib/fun_with_flags/store/persistent/ecto/null_repo.ex:9: FunWithFlags.NullEctoRepo.all/1
    (fun_with_flags 1.5.1) lib/fun_with_flags/store/persistent/ecto.ex:32: FunWithFlags.Store.Persistent.Ecto.get/1
    (fun_with_flags 1.5.1) lib/fun_with_flags/store.ex:15: FunWithFlags.Store.lookup/1
    (fun_with_flags 1.5.1) lib/fun_with_flags.ex:77: FunWithFlags.enabled?/2

Checking the config in the same iex session:

iex|2 β–Ά Application.get_env(:fun_with_flags, :persistence)
[
  adapter: FunWithFlags.Store.Persistent.Ecto,
  repo: Plus.Repo,
  ecto_table_name: "fun_with_flags_toggles"
]

What am I missing?

Configure set of permitted flags

First, congrats on the lib! I really like the features and it's being super useful :)

One thing that I was feeling the need, is to somehow configure a set of the available flags, something like this:

config :fun_with_flags, 
  permitted_flags: [
    :feature_a,
    :feature_b,
    :feature_c,
  ]

I can see 2 advantages of having this:

  1. When we are calling functions like FunWithFlags.enabled?(:feature_a) or FunWithFlags.enable(:feature_a), we validate against the permitted flags. Useful to ensure there's not typo in the flag name, for example.
  2. Listing the available flags in the UI even if they don't exist in the storage. This is because I always need to check the code before add them in through the UI, not a big deal, bit if they are already listed it would be a very nice thing :)

The 1st advantage could required a relatively big refactoring and for me is not that important. It's just a plus, I think.

The 2nd advantage I really like to have it, since it makes easier to work only with the UI. This could be implemented in 2 ways, I think:

  1. Implementing in this repository, doing the logic inside all_flag_names/0 and all_flags/0
  2. Implementing in the https://github.com/tompave/fun_with_flags_ui repository.

I guess I could "override" the storage I'm using, creating my own adapter and doing these logics for the all_flag_names/0 and all_flags/0, but having this in the library can be easier for users :)

Do you think this is a valid feature to having in this library?

Fix small typo

Great lib! Found a small typo, thought I'd remove it. #37. Hope this helps!

[Question] How to toggle flag in production?

Production servers in my scenario cannot run iex, so DevOps can't toggle the feature flags using the functions described in the documentation.

In essence, my question is, will toggling the flag in Redis store have the desired effect with compiled code?

Error in multi tenant apps with foreign key

Hey,

first of all, thanks for release this great library for us all. I think I've found an issue when using Fun With Flags with a multi tenant app. I've implemented my multi tenant model following this ecto guide: https://hexdocs.pm/ecto/multi-tenancy-with-foreign-keys.html.

The problem is when I try to enable/disable a flag, I got the error:

** (RuntimeError) expected org_id or skip_org_id to be set

Obviously, the error occurs because FWF doesn't know anything about my tenant model. I propose to add a fun_with_flags: true option for every call of Ecto.Repo inside the lib. By doing that, I can figure out which queries are originated from Fun With Flags and execute these queries outside my tenant model, like this:

defmodule MyApp.Repo do
  use Ecto.Repo, otp_app: :my_app

  require Ecto.Query

  def prepare_query(operation, query, opts) do
    cond do
      opts[:skip_org_id] || opts[:schema_migration] || opts[:fun_with_flags] ->
        {query, opts}

      org_id = opts[:org_id] ->
        {Ecto.Query.where(query, org_id: ^org_id), opts}

      true ->
        raise "expected org_id or skip_org_id to be set"
    end
  end
end

I can work on that and send you a pull request. Do you think there's a chance for you to merge it?

v1.4.0 release date?

Hello @tompave

Is v1.4.0 going to be released soon(ish)?
I really like the changes you introduced with the behavior for Persistance that allows to create custom adapters, curious if it's going to be available in the upcoming days to try it out.

[error] FunWithFlags: It looks like you're trying to use FunWithFlags.Store.Persistent.Redis to persist flags, but you haven't added its optional dependency to the Mixfile of your project.

Hey there!

Im trying to use the lib but it seems that I can't make it work with the basic configuration.

[error] FunWithFlags: It looks like you're trying to use FunWithFlags.Store.Persistent.Redis to persist flags, but you haven't added its optional dependency to the Mixfile of your project.

In my config.exs I have:

# FunWithFlags configuration.
config :fun_with_flags, :persistence,
  adapter: FunWithFlags.Store.Persistent.Ecto,
  repo: EventsApi.Repo

I do have in my mix file

    {:ecto_sql, "~> 3.0}", 
    {:phoenix_pubsub, "~> 1.1"},

Elixir version:

Erlang/OTP 22 [erts-10.4.4] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [hipe] [dtrace]

Elixir 1.9.2 (compiled with Erlang/OTP 20)

I haven't found anything else in the docs that need to be changed. What am I doing wrong?

Thanks

Usage with PostgresSQL schemas

I have a Phoenix Umbrella project with a bunch of apps using PostgreSQL. Each app is configured with a @schema_prefix schema attribute so that they can all access the same database, but using different schemas. That way I can keep each app's data logically separated from the others, without the need to create multiple databases in the same PostgreSQL instance, for example.

However, when I try do use FunWithFlags within one of these apps, the lib tries to find the table on the default public schema, and of course, it can't find it and it errors out. Is there a way to configure this in FunWithFlags? I've tried a few things like including the schema name in the option ecto_table_name, but with no success.

Setting cache `enabled: false` crashes the app

If we disable the cache in config via setting the enabled flag to false then the app crashes when trying to save a new feature flag:

exited in: GenServer.call(FunWithFlags.Store.Cache, {:put, %FunWithFlags.Flag{gates: [%FunWithFlags.Gate{enabled: false, for: nil, type: :boolean}], name: :asd}}, 5000) ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started

Is this setting meant to be used in some other way?

Spec definition and documentation mismatch

Thank you for creating this library. I use it often. I recently added rules for groups and the dialyzer caught that I wasn't using atoms for groups. On further investigation it looks as though FunWithFlagsUI inserts the group rule into the database as a String and is passed to the in? function as a String (https://github.com/tompave/fun_with_flags/blob/master/lib/fun_with_flags/protocols/group.ex#L68). But, the specs specify that the group should be an atom. As far as I can tell it comes from the database unchanged:

iex(99)> FunWithFlags.get_flag(:bar)
2019-12-04 14:11:39.911 [info] [application=ecto_sql module=Ecto.Adapters.SQL function=log/4 pid=<0.1619.0> ] QUERY OK source="fun_with_flags_toggles" db=0.7ms
SELECT DISTINCT f0.`flag_name` FROM `fun_with_flags_toggles` AS f0 []
%FunWithFlags.Flag{
  gates: [
    %FunWithFlags.Gate{enabled: false, for: nil, type: :boolean},
    %FunWithFlags.Gate{
      enabled: true,
      for: "inserted_at<2019-11-8",
      type: :group
    }
  ],
  name: :bar
}

The documentation also shows the usage as strings (https://github.com/tompave/fun_with_flags#group-gate). My guess is that the spec should be atom() | String.t() or term().

Feature Proposal: Audit Logging

It would be great to have the ability to track who toggled what features and to what. This can be useful both from a troubleshooting standpoint but also to satisfy auditing/compliance requirements.

It would also be lovely if this worked with https://github.com/tompave/fun_with_flags_ui, the more seamless the better. As such this proposal encompasses both libraries, although the changes to FunWithFlags do stand on their own.

FunWithFlags changes.

I'd like to add a new function:
FunWithFlags.add_audit_log(action, flag_name, current_user, opts)

It would be called like so:

FunWithFlags.add_audit_log(:enable, :my_feature, 1234, [for_percentage_of: {:time, ratio}])

Under the hood this would call a new callback on the Persistent behavior:

c:FunWithFlags.Store.Persistent.add_audit_log/4 which we'd implement for both
Redix and Ecto.

These function implementations would simply persist an audit log to either a new redis key or a new postgres table. This would require updating the readme to include this new table in a migration (shouldn't be difficult to make this optional).

Additionally, it could be useful to also add another function to the Persistent behavior which wraps other things in a transaction. This would be useful in fun_with_flags_ui portion of these changes to ensure that we don't have situations where an audit log gets added but the feature flag doesn't change and vice versa. Thinking a function signature would look something like:

c:FunWithFlags.Store.Persistent.transaction/1 which would just call Ecto.Transaction or Redix.transaction_pipeline/3 essentially.

All of the above changes I believe can stand alone as useful features to FunWithFlags.

FunWithFlags.UI changes.

There are a number of places in FunWithFlagsUI that call FunWithFlags.enable/2,FunWithFlags.disable/2, and FunWithFlags.clear/2 directly.

The idea is to wrap those calls in new functions defined in FunWithFlags.UI.Utils that call out to the previously mentioned FunWithFlags.Store.Persistent.transaction/1 and then calls FunWithFlags.enable/2,FunWithFlags.disable/2, or FunWithFlags.clear/2, as well as FunWithFlags.add_audit_log/4.

The one missing piece at this point is the method of retrieving the current user.

Would love feedback on this next part:

We could make some assumptions or have some opinions.

We could enforce that the current user is always in conn.assigns.fun_with_flags_current_user and leave it up to the users of FunWithFlags.UI to add a plug which puts the current user there.

Or we could allow users to define a function that accepts the %Plug.Conn{} struct as an argument and spits out the current user. This is way more flexible, and doesn't force users to add something to assigns, but it's slightly more elaborate to configure. You'd have to pass a reference to this function into application config.

We could also go even further and define a behavior with a callback that accepts a %Plug.Conn{} struct and outputs the current user, and then force users to implement that callback on a module that they can then pass in as config. This is also a bit elaborate.

Regardless of which one we go with, we'd want to configure this behavior in application config and ensure that if no :audit_logging config is supplied (should be the state of all things that have implemented this to date), then things simply work and no audit logging is attempted. This should be an opt-in feature.

Audit Logging UI

The above is all we need to get audit logging implemented and working fairly seemlessly, but we still wouldn't have a way to actually view the persisted audit logs without looking in Redis or Postgres itself. So we'd probably want to add a UI in FunWithFlags.UI for viewing the audit logs related to a given key. This could happen separately from all of the other changes.

In Conclusion

What do you think of this proposal? I think I understand enough about this project at this point to make these changes, but super open to feedback!

If you think this sounds like a reasonable new feature, I'd be happy to implement it. The only breaking changes I could think of is the additional callbacks on the FunWithFlags.Store.Persistent behavior, but at the very least it should warn people that have written custom persistence modules at compile time that they need to implement a new thing.

I'm confident that this can be built in a way that doesn't disrupt existing users (beyond those that have custom persistence) and allows a user to not configure audit logging and simply ignore this feature.

GenServer FunWithFlags.Notifications.PhoenixPubSub terminating

This issue is cropping up randomly when trying to run our Phoenix server. Sometimes it fails hard, sometimes it works fine. It's unclear if this is an actual issue with FunWithFlags, or if it's a knock-on issue from something else, so please bear with me. Also, the issue appears to involve Logger to some extent.

elixir: "~> 1.6"

Logger config:

config :logger, :console,
  format: "$time $metadata[$level] $message\n",
  metadata: [:request_id]

FunWithFlags config:

config :fun_with_flags, :cache,
  enabled: true,
  ttl: 300

config :fun_with_flags, :persistence,
  adapter: FunWithFlags.Store.Persistent.Ecto,
  repo: Planswell.Api.Repo

config :fun_with_flags, :cache_bust_notifications,
  enabled: true,
  adapter: FunWithFlags.Notifications.PhoenixPubSub,
  client: lanswell.Api.PubSub

Stack trace:

11:42:17.890 [error] GenServer FunWithFlags.Notifications.PhoenixPubSub terminating
** (RuntimeError) Tried to subscribe to Phoenix.PubSub process Planswell.Api.PubSub 5 times. Giving up.
    (fun_with_flags) lib/fun_with_flags/notifications/phoenix_pubsub.ex:83: FunWithFlags.Notifications.PhoenixPubSub.subscribe/1
    (fun_with_flags) lib/fun_with_flags/notifications/phoenix_pubsub.ex:117: FunWithFlags.Notifications.PhoenixPubSub.handle_info/2
    (stdlib) gen_server.erl:616: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:686: :gen_server.handle_msg/6
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Last message: {:subscribe_retry, 6}
==> common

=ERROR REPORT==== 26-Mar-2018::11:42:18 ===
** Generic server 'Elixir.FunWithFlags.Notifications.PhoenixPubSub' terminating 
** Last message in was {subscribe_retry,2}
** When Server state == <<"FUUOChZ9VDiTI3sxNTIyLCA3ODkzNywgNjgyNzA5fQ">>
** Reason for termination == 
** {#{'__exception__' => true,'__struct__' => 'Elixir.RuntimeError',
      message =>
          <<"cannot use Logger, the :logger application is not running">>},
    [{'Elixir.Logger.Config','__data__',0,
         [{file,"lib/logger/config.ex"},{line,53}]},
     {'Elixir.Logger',bare_log,3,[{file,"lib/logger.ex"},{line,613}]},
     {'Elixir.FunWithFlags.Notifications.PhoenixPubSub',handle_info,2,
         [{file,"lib/fun_with_flags/notifications/phoenix_pubsub.ex"},
          {line,116}]},
     {gen_server,try_dispatch,4,[{file,"gen_server.erl"},{line,616}]},
     {gen_server,handle_msg,6,[{file,"gen_server.erl"},{line,686}]},
     {proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,247}]}]}

=INFO REPORT==== 26-Mar-2018::11:42:18 ===
    application: fun_with_flags
    exited: shutdown
    type: temporary
** (Mix) Could not start application api: {:not_running, :fun_with_flags}

Non-boolean variations

G'day! Having already had to use at least one tri-state variation on our previous solution, I'm nervous about solving the same kind of problem by strapping together FWF's boolean flags. All off is fine, but >1 on would be confusing.

Don't suppose we could support any atom, not just :true and :false? It'd need an Ecto migration to migrate that boolean enabled to a string state… what else?

Store provider is set on compile time

Hello πŸ‘‹

when working with FunWithFlags in our test suite we noticed that we have to recompile the fun_with_flags dependency every time we changed the config around the storage provider (we wanted to simply disable the cache in the test environment).

 ** (exit) exited in: GenServer.call(FunWithFlags.Store.Cache, {:put, %FunWithFlags.Flag{gates: [%FunWithFlags.Gate{enabled: true, for: "user:...", type: :actor}], name: :foo}}, 5000)
 ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't starte

This happens, because the config is stored in a module variable:
https://github.com/tompave/fun_with_flags/blob/master/lib/fun_with_flags.ex#L30

So when we changed the config to disable caching, it would still use the StoreProvider instead of the SimpleStoreProvider, but with the cache process not started.

Would it make sense to move this call from a module variable @store into a function store(), so that the config is still changeable without needing to recompile the project?

I can happily help out and open a PR if you agree :)

Also thank you for creating this great project btw πŸ’š. It really is a joy to use and helps us a great deal in our workflows.

Cheers
Andi

Config param `cache: enabled` results in exception

Hi!
We've been using your library for some time - it's wonderful, thanks for all the effort you put into it.

We've started noticing a strange pattern when using FunWithFlags for unit tests. Our setup consists of a postgres table that is used as the backend for the library, where all the flags are saved. We opted for the db backend in tests since Phoenix offers isolation for accessing the database during tests, while there is no such thing for redit.

However, the fact that your library implements a caching mechanism via ETS seems to provide shared behaviour between tests, which we want to avoid.

Thus we set the following params inside our test config:

config :fun_with_flags, :cache, enabled: false

And we keep getting crashes because the library tries to call the cache genserver anyway... The offending function seems to be this one, which gets called after each operation of reading/saving flags, whatever the value of the :cache property is.

I think it would be pretty easy to check the config before calling Cache.put or Cache.get, in order to avoid crashes... What do you think?

FunWithFlags.Supervisor not loaded when using mix releases

In order to make the use of the new FunWithFlags.Supervisor, I added runtime: false to both fun_with_flags and fun_with_flags_ui, but it failed to run in production with an error that FunWithFlags.Supervisor is not found.

The following seems to fix the problem:

      releases: [
        myapp: [
          applications: [fun_with_flags: :load, fun_with_flags_ui: :load]
        ]
      ]

Is there something I'm missing or should the above be added to the docs for the people who are using mix release to package their app?

Feature Request: Postgres/Ecto support

Currently Molasses is the only feature flag library in Elixir available that has Postgres backing as an option, though I consider the abstraction of groups much cleaner in this library. Would it be possible to add a persistence layer for Postgres?

Looking through the code, I was looking for where that would get hooked in. as some sort of generic "Store Protocol/Behaviour", to see if that would even be possible with the current implementation, but the current stores (simple_store.ex and store.ex) don't seem to reference one. FunWithFlags.Store.Persistent seems to be hard coded to start a Redis worker, so perhaps that being made into a pluggable interface of sorts would more easily allow this, potentially opening the door to Mongo adapters or RethinkDB adapters or even Application config backed adapters.

'conflict_target' in Ecto persistence prevents use with MySQL

Howdy! I'm attempting to integrate fun_with_flags into a Phoenix app that's using a MySQL database. Unfortunately, at the moment it doesn't quite behave as expected:

iex(1)> FunWithFlags.enable(:test_flag)
** (ArgumentError) The :conflict_target option is not supported in insert/insert_all by MySQL
    (ecto_sql) lib/ecto/adapters/mysql/connection.ex:972: Ecto.Adapters.MySQL.Connection.error!/2
    (ecto_sql) lib/ecto/adapters/mysql/connection.ex:149: Ecto.Adapters.MySQL.Connection.insert/6
    (ecto_sql) lib/ecto/adapters/mysql.ex:208: Ecto.Adapters.MySQL.insert/6
    (ecto) lib/ecto/repo/schema.ex:651: Ecto.Repo.Schema.apply/4
    (ecto) lib/ecto/repo/schema.ex:264: anonymous fn/15 in Ecto.Repo.Schema.do_insert/3
    (fun_with_flags) lib/fun_with_flags/store/persistent/ecto.ex:193: FunWithFlags.Store.Persistent.Ecto.do_insert/3
    (fun_with_flags) lib/fun_with_flags/store/persistent/ecto.ex:71: FunWithFlags.Store.Persistent.Ecto.put/2
    (fun_with_flags) lib/fun_with_flags/store.ex:36: FunWithFlags.Store.put/2
    (fun_with_flags) lib/fun_with_flags.ex:185: FunWithFlags.enable/2

It should be possible to add this option to the insert statement dynamically, based on the result of Repo.__adapter__ (AFAIK, MySQL is the special case here) - but I'm not certain if that's the best way to approach this.

If you'd welcome a patch implementing the above suggestion, let me know.

"Looks like ... Redis" when using ecto

This isn't a bug, but I did fumble through getting fun_with_flags set up for a while. I turns out I wasn't planning on using caching because I was working in dev/test environment trying to set up fun_with_flags with Ecto. I kept getting an error message despite setting Ecto as my adapter saying that I was missing a Redis dependency.

It turns out this error was specifically because I had neglected to turn caching off and it defaults to Redis.

Do you think this is better addressed in the README or modifying the error message to reflect that you can also simply disable caching.

Can't change config without doing a `mix deps.clean fun_with_flags`.

Since fun_with_flags sets the config at compile time, when the config is changed, you have to do a mix deps.clean fun_with_flags. If you don't, when you compile the app, you get a

ERROR! the application :fun_with_flags has a different value set for key :cache during runtime compared to compile time. Since this application environment entry was marked as compile time, this difference can lead to different behaviour than expected:

  * Compile time value was not set
  * Runtime value was set to: [enabled: true]

To fix this error, you might:

  * Make the runtime value match the compile time one

  * Recompile your project. If the misconfigured application is a dependency, you may need to run "mix deps.compile fun_with_flags --force"

  * Alternatively, you can disable this check. If you are using releases, you can set :validate_compile_env to false in your release configuration. If you are using Mix to start your system, you can pass the --no-validate-compile-env flag


==> fun_with_flags
could not compile dependency :fun_with_flags, "mix compile" failed. Errors may have been logged above. You can recompile this dependency with "mix deps.compile fun_with_flags", update it with "mix deps.update fun_with_flags" or clean it with "mix deps.clean fun_with_flags"

This causes problems with our CI run, and requires us to issue a mix deps.clean fun_with_flags as one of the commands so our builds won't get stuck.

[RFC] Allow disabled gates for % of Actor

The docs specify the following:

The percentage gates can only express an enabled state, as disabling something for a percentage of time or actors is logically equivalent to enabling it for the complementary percentage.

However, because actor gates override group gates, there is a hole in certain contexts.

For instance, if we want to allow all members of a group to have access to a new feature, but limit that group to a subset of actors, this is currently not possible as the actor gate overrides. For this to work, one would have to enable the group, but disable a % of the actors.

An alternative strategy, albeit one with significant change, would be to evaluate all flags, and only pass if all resolve true. The same override functionality as currently exists could be achieved by providing a veto flag on a given gate.

Perhaps we're simply missing something about the setup though, and this type of subset limiting already exists.

Schema prefix support for Ecto

Would it be possible to add schema support for ecto via prefixes so funwithflags can live in a separate schema in postgres. Useful in a multi-tenancy situation.

I can work around it by customizing the persistence adapter, but it is a fairly simple fix, and happy to submit a patch (I don't know about how it would work in MySQL). Will require changes in two files: ecto.ex and config.ex and also introduce a new name called ecto_table_prefix

lobo

New release with PR #54

Anything planned for the next release? I need the content of #54
Do you have any feature in mind to include? Maybe I can help you πŸ˜„

In memory store

I was previously using the fun_with_flags_in_memory library for getting/setting feature flags in our test environment. The repo now seem to have been deleted, and the package isn't compatible with 1.5 as it depends on 1.4.0. Would it make sense to bring this store into the main library, explicitly for people unit testing? If not, is there another preferred option for this use case?

Is there any reason flag names must be atoms instead of strings?

The only benefits of atom names I can come up with are slightly faster comparison and less memory usage, but I don't think they would make a considerable difference for a real app. And I didn't even take into account that flag names are already converted to strings (and vice versa) by persistence adapters.

The issues with atom names arise when considering flag names that come from external input (suppose I want to provide this library to non-elixir services by wrapping it in JSON API). Because we cannot just convert arbitrary strings to atoms, it would require using String.to_existing_atom/1 and rescuing from exceptions in case the flag name does not exist (as you already do in the web UI).

Persistence config not recognized in release

Versions

erlang 23.1
elixir 1.11.3-otp-23
fun_with_flags 1.6.0

Problem

When running our application in a production release FWF doesn't seem to be accessing our configs/persistence adapter and is raising an exception when trying to call FunWithFlags.all_flags().

Config:

config :fun_with_flags, :persistence,
  adapter: FunWithFlags.Store.Persistent.Ecto,
  repo: MyApp.Repo

Exception raised when visiting the /feature-flags/flags endpoints of the UI:

function FunWithFlags.Store.Persistent.Redis.all_flags/0 is undefined (module FunWithFlags.Store.Persistent.Redis is not available)

The application runs without issues in the local dev environment. I see in 1.5.1 there were some related changes and that there is an open issue related to running the app in a production release but I haven't been able to identify the cause, any insight would be greatly appreciated.

Thanks!

Cannot compile without redix optional dependency.

On Elixir 1.9.1, Erlang 21, when I try to get the project working with only the

{:fun_with_flags, "~> 1.4.1"},

line to mix.exs and then add the following configuration to a brand new phoenix project:

config :fun_with_flags, :cache_bust_notifications,
  enabled: true,
  adapter: FunWithFlags.Notifications.PhoenixPubSub,
  client: Flaggy.PubSub

config :fun_with_flags, :persistence,
  adapter: FunWithFlags.Store.Persistent.Ecto,
  repo: Flaggy.Repo

(Flaggy is the name of the phoenix app)
(I'm trying to avoid redis entirely)

I get the following compile error:

code/elixir/flaggy mix phx.server
Compiling 13 files (.ex)
Generated flaggy app
[error] FunWithFlags: It looks like you're trying to use FunWithFlags.Store.Persistent.Redis to persist flags, but you haven't added its optional dependency to the Mixfile of your project.
[info] Application fun_with_flags exited: exited in: FunWithFlags.Application.start(:normal, [])
    ** (EXIT) an exception was raised:
        ** (UndefinedFunctionError) function FunWithFlags.Store.Persistent.Redis.worker_spec/0 is undefined (module FunWithFlags.Store.Persistent.Redis is not available)
            FunWithFlags.Store.Persistent.Redis.worker_spec()
            (fun_with_flags) lib/fun_with_flags/application.ex:28: FunWithFlags.Application.persistence_spec/0
            (fun_with_flags) lib/fun_with_flags/application.ex:17: FunWithFlags.Application.children/0
            (fun_with_flags) lib/fun_with_flags/application.ex:10: FunWithFlags.Application.start/2
            (kernel) application_master.erl:277: :application_master.start_it_old/4
** (Mix) Could not start application fun_with_flags: exited in: FunWithFlags.Application.start(:normal, [])
    ** (EXIT) an exception was raised:
        ** (UndefinedFunctionError) function FunWithFlags.Store.Persistent.Redis.worker_spec/0 is undefined (module FunWithFlags.Store.Persistent.Redis is not available)
            FunWithFlags.Store.Persistent.Redis.worker_spec()
            (fun_with_flags) lib/fun_with_flags/application.ex:28: FunWithFlags.Application.persistence_spec/0
            (fun_with_flags) lib/fun_with_flags/application.ex:17: FunWithFlags.Application.children/0
            (fun_with_flags) lib/fun_with_flags/application.ex:10: FunWithFlags.Application.start/2
            (kernel) application_master.erl:277: :application_master.start_it_old/4

When I add redix to mix.exs everything works normally. It's not terrible to add the redix library, but I would like to not, as I'm not actually using it for anything.

Exception when using Ecto for persistence without cache

I set up FunWithFlags with the following config. (I'm running a low volume site on a single node, and was happy to take the performance hit to not have to set up Redis.)

config :fun_with_flags, :cache, enabled: false
config :fun_with_flags, :cache_bust_notifications, enabled: false

config :fun_with_flags, :persistence,
  adapter: FunWithFlags.Store.Persistent.Ecto,
  repo: Poplar.Repo

It worked absolutely fine in dev, but when I deployed to production (compiled into a release) I got the following exception on boot:

13:26:29.420 [info] Application fun_with_flags exited: exited in: FunWithFlags.Application.start(:normal, [])
** (EXIT) an exception was raised:
** (UndefinedFunctionError) function FunWithFlags.Store.Persistent.Redis.worker_spec/0 is undefined (module FunWithFlags.Store.Persistent.Redis is not available)
FunWithFlags.Store.Persistent.Redis.worker_spec()
(fun_with_flags) lib/fun_with_flags/application.ex:16: FunWithFlags.Application.children/0
(fun_with_flags) lib/fun_with_flags/application.ex:10: FunWithFlags.Application.start/2
(kernel) application_master.erl:277: :application_master.start_it_old/4

Is this a problem with my configuration, or have I hit a bug in the library? Thanks!

Cluster-wide flags

This project uses pubsub now, but the issue of what to do if a node joins, how to tell if the local cache is good, etc, is one of those notoriously sticky wickets.

If it used Phoenix.Tracker, it could have stronger guarantees about whether a feature 'should be' on or off cluster-wide, without worrying as much about keeping a local ets in sync itself -- at a cost of potentially increased latency in rolling out flag changes (just because of time required to be sure of consensus compared to straight pubsub w/o consensus, not because Tracker's not performant at running tons of feature flag lookups, which it would be). But tuning the tracker ttls downwards works really well, and a set of feature flags, even per-user ones, is the sort of thing that Tracker should be good at given its biggest use case.

That stronger consistency could also be helpful for other features, like occasionally persisting the feature flags, because you'd know you're persisting something consistent, not a local cache that might be out of sync.

Request: Consider improving the documentation to be a little more straightforward

Hi!

I guess I have a good use case to use this lib in my application but I found the documentation just overwhelmingly wordy. Also, I could not quite comprehend what configuration I need after reading the whole README file - I still don't quite know how to configure this for my use-case. For example: Do I need to configure cache_bust_notifications, PubSub adapters, and whatnot to just use this with Phoenix and Ecto?.

One thing that I've missed is a Getting Started section that goes about installing and configuring the library. Right now, both the configuration and installation sections are almost at the end of the README file and a lot of things seem to be explained in parallel to each other.

My suggestion is to extract the installation, configuration, and usage sections into a Get Started, struct and explain them in a little bit more focused fashion and let the miscellaneous part at the end (Web Dashboard, Origin, Caching, PubSub, etc). If possible, provide a complete installation/ configuration example for the simplest use-case, using the Ecto adapter in a Phoenix app, and then talk about using Redis and extensibility.

ΒΉBTW: Why don't you use Ecto or something like Mnesia as the default configuration for persistence? I think that there's a lot of Elixir libs that over-rely on external dependencies to do this without actually needing it. I'm not quite sure why would Redisbe necessary for something like this.

Improved runtime config support

Hi - I want to use this library in our app, however because we run it in multiple different environments from a single release, all our config comes in through environment variables (we also do this to supply passwords without putting them in hardwired config).

While it's possible to provide a Redis URL using the {:system, url} magic, it's kind of clunky trying to munge the individual bits of config (SSL, password, server etc) into another environment variable before startup, and seems like pointless effort when internally Redix is just going to split them all up again. Plus it still doesn't allow any other bits (TTL etc) to be configured dynamically.

There's generally two ways to fix this that I've seen - my preferred one is to add generalised support using Confex. That requires no changes to the config spec, but allows each individual field to be specified as {:system, :type, "ENV_VAR", default}.

However some people don't like having an added dependency in their library, so an alternative is to allow something in the config like {:callback, Module, :function} and have the application provide a function which returns config.

I'm happy to implement whichever of those you prefer (or some alternative) - just let me know.

null value in column "id" violates not-null constraint

I was getting an error message when enabling a flag from postgrex:

** (Postgrex.Error) ERROR 23502 (not_null_violation): null value in column "id" violates not-null constraint

In my application I have Ecto configured to use UUIDs by default. You could add some logic to the Record field to figure this out probably, although, it may be simpler to modify the migration for the table to explicitly set the primary key to an serial type:

create table(:fun_with_flags_toggles, primary_key: false) do
  add :id, :serial, primary_key: true
  add :flag_name, :string
  add :gate_type, :string
  add :target, :string
  add :enabled, :boolean
end

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.