Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
58 changes: 26 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,52 @@

A small Elixir application showing how to consume the published [Featurevisor Elixir SDK](https://github.com/featurevisor/featurevisor-elixir) from Hex.pm.

The application downloads a Featurevisor datafile, creates an SDK instance, and evaluates one feature as a flag, variation, and string variable.
Learn more about Featurevisor [here](https://featurevisor.com).

It fetches a production datafile, creates a Featurevisor instance with customer context, then evaluates:

- the `commerce_platform` feature as a flag;
- the `checkout_experience` variation;
- integer and array variables from `checkout_experience`;
- the `serviceEndpoints` global object variable;
- the `supportContact` global string variable.

## Requirements

Elixir 1.15 or newer.

## Installation
## Run the example

```sh
mix deps.get
mix run -e 'FeaturevisorExampleElixir.run()'
```

## Run it
The datafile comes from the [Featurevisor Cloudflare example](https://github.com/featurevisor/featurevisor-example-cloudflare):

```sh
mix run -e 'FeaturevisorExampleElixir.run()'
```text
https://featurevisor-example-cloudflare.pages.dev/production/featurevisor-sdk-v3.json
```

The application fetches this production datafile:
Expected output:

```text
https://featurevisor-example-cloudflare.pages.dev/production/featurevisor-mobile.json
Commerce platform enabled: true
Checkout variation: express
Maximum checkout items: 25
Payment methods: ["card", "wallet"]
Service endpoint: https://api.eu.example.com (timeout: 1200 ms, retries: 4)
Support contact: support-nl@example.com
```

The important SDK usage is in `lib/featurevisor_example_elixir.ex`:

```elixir
f = Featurevisor.create_featurevisor(%{
datafile: datafile,
context: %{
"userId" => "mobile-user",
"country" => "nl"
}
})
The important SDK usage is in `lib/featurevisor_example_elixir.ex`. Change its context values to see how Featurevisor selects different rules, variations, and global variable overrides.

enabled = Featurevisor.enabled?(f, "mobile_experience")
variation = Featurevisor.get_variation(f, "mobile_experience")
The `featurevisor` dependency comes from Hex.pm. It is not a local path dependency, so this project demonstrates the same setup used by a new consumer.

welcome_message =
Featurevisor.get_variable_string(
f,
"mobile_experience",
"welcome_message"
)
## Checks

Featurevisor.close(f)
```sh
make check
```

The `featurevisor` dependency comes from Hex.pm. It is not a local path dependency, so this project demonstrates the same setup used by a new consumer.

Learn more in the [Featurevisor Elixir SDK documentation](https://featurevisor.com/docs/sdks/elixir/).

## Featurevisor project

The datafile is generated by the [Featurevisor example Cloudflare project](https://github.com/featurevisor/featurevisor-example-cloudflare).
44 changes: 32 additions & 12 deletions lib/featurevisor_example_elixir.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
defmodule FeaturevisorExampleElixir do
@moduledoc "A small application demonstrating the Featurevisor Elixir SDK."

@datafile_url "https://featurevisor-example-cloudflare.pages.dev/production/featurevisor-mobile.json"
@context %{"userId" => "mobile-user", "country" => "nl"}
@datafile_url "https://featurevisor-example-cloudflare.pages.dev/production/featurevisor-sdk-v3.json"
@context %{
"userId" => "customer-123",
"country" => "nl",
"locale" => "nl-NL",
"accountPlan" => "pro"
}

def run(datafile_url \\ @datafile_url) do
response = Req.get!(datafile_url)
Expand All @@ -17,27 +22,42 @@ defmodule FeaturevisorExampleElixir do
end

def evaluate(datafile, context \\ @context) do
f = Featurevisor.create_featurevisor(%{datafile: datafile, context: context})
f =
Featurevisor.create_featurevisor(%{
datafile: datafile,
context: context,
log_level: :error
})

try do
%{
revision: Featurevisor.get_revision(f),
enabled: Featurevisor.enabled?(f, "mobile_experience"),
variation: Featurevisor.get_variation(f, "mobile_experience"),
welcome_message:
Featurevisor.get_variable_string(f, "mobile_experience", "welcome_message")
commerce_enabled: Featurevisor.enabled?(f, "commerce_platform"),
checkout_variation: Featurevisor.get_variation(f, "checkout_experience"),
max_items: Featurevisor.get_variable_integer(f, "checkout_experience", "max_items"),
payment_methods:
Featurevisor.get_variable_array(f, "checkout_experience", "payment_methods"),
service_endpoints: Featurevisor.get_global_variable_object(f, "serviceEndpoints"),
support_contact: Featurevisor.get_global_variable_string(f, "supportContact")
}
after
Featurevisor.close(f)
end
end

defp print_results(results) do
IO.puts("Datafile revision: #{results.revision}")
IO.puts("Feature 'mobile_experience' enabled: #{results.enabled}")
IO.puts("Variation: #{results.variation || "none"}")
IO.puts("Variable 'welcome_message': #{results.welcome_message || "none"}")
endpoints = results.service_endpoints

IO.puts("Commerce platform enabled: #{results.commerce_enabled}")
IO.puts("Checkout variation: #{results.checkout_variation || "unavailable"}")
IO.puts("Maximum checkout items: #{results.max_items || "unavailable"}")
IO.puts("Payment methods: #{inspect(results.payment_methods || [])}")

IO.puts(
"Service endpoint: #{endpoints["baseUrl"]} " <>
"(timeout: #{endpoints["timeoutMs"]} ms, retries: #{endpoints["retries"]})"
)

IO.puts("Support contact: #{results.support_contact || "unavailable"}")
:ok
end
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule FeaturevisorExampleElixir.MixProject do

defp deps do
[
{:featurevisor, "~> 0.1.0"},
{:featurevisor, "~> 1.0.0"},
{:req, "~> 0.5"}
]
end
Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
%{
"featurevisor": {:hex, :featurevisor, "0.1.0", "6270e132fca891cd60f556c04869379d8671c6308df934ddebaa235ba4bd05a2", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f058ea23939af508d148647d4bc325f8eecbdb59aa9906f918d7de68479dfe38"},
"featurevisor": {:hex, :featurevisor, "1.0.0", "f24d4fe45a99338d518d65db1c0e8e43c535b616f97b607680d4c2c5091f8f98", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "2dcb4a5f7696c087c597e0af60e5f4d19dd999f3c3d0181d6ef640c350cb2450"},
"finch": {:hex, :finch, "0.23.0", "e3f9287ac25a8832f848b144c2b57346aac65b205e2e0629a52adfe6507fd837", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.8", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "80e58d3f936f57e3fdf404f83a3642897ae6d9fb642934e46da4d8fe761b99d5"},
"hpax": {:hex, :hpax, "1.0.4", "777de5d433b0fbdc7c418159c8055910faa8047ffdb3d6b31098d2a46cd7685c", [:mix], [], "hexpm", "afc7cb142ebcc2d01ce7816190b98ce5dd49e799111b24249f3443d730f377ca"},
"jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"},
Expand Down
60 changes: 42 additions & 18 deletions test/featurevisor_example_elixir_test.exs
Original file line number Diff line number Diff line change
@@ -1,45 +1,69 @@
defmodule FeaturevisorExampleElixirTest do
use ExUnit.Case, async: true

test "evaluates a flag, variation, and variable" do
test "evaluates flags, variations, feature variables, and global variables" do
assert FeaturevisorExampleElixir.evaluate(datafile()) == %{
revision: "test",
enabled: true,
variation: "treatment",
welcome_message: "Welcome to Featurevisor"
commerce_enabled: true,
checkout_variation: "express",
max_items: 25,
payment_methods: ["card", "wallet"],
service_endpoints: %{
"baseUrl" => "https://api.eu.example.com",
"timeoutMs" => 1200,
"retries" => 4
},
support_contact: "support-nl@example.com"
}
end

defp datafile do
%{
"schemaVersion" => "2",
"featurevisorVersion" => "3.5.0",
"featurevisorVersion" => "3.7.0",
"revision" => "test",
"segments" => %{},
"features" => %{
"mobile_experience" => %{
"key" => "mobile_experience",
"commerce_platform" => %{
"bucketBy" => "userId",
"variations" => [
%{"value" => "control"},
%{"value" => "treatment"}
],
"force" => [%{"segments" => "*", "enabled" => true}],
"traffic" => []
},
"checkout_experience" => %{
"bucketBy" => "userId",
"variations" => [%{"value" => "express", "weight" => 100}],
"variablesSchema" => %{
"welcome_message" => %{
"type" => "string",
"defaultValue" => "Welcome"
}
"max_items" => %{"type" => "integer", "defaultValue" => 10},
"payment_methods" => %{"type" => "array", "defaultValue" => ["card"]}
},
"force" => [
%{
"segments" => "*",
"enabled" => true,
"variation" => "treatment",
"variables" => %{"welcome_message" => "Welcome to Featurevisor"}
"variation" => "express",
"variables" => %{
"max_items" => 25,
"payment_methods" => ["card", "wallet"]
}
}
],
"traffic" => []
}
},
"variables" => %{
"serviceEndpoints" => %{
"type" => "object",
"defaultValue" => %{
"baseUrl" => "https://api.eu.example.com",
"timeoutMs" => 1200,
"retries" => 4
},
"overrides" => []
},
"supportContact" => %{
"type" => "string",
"defaultValue" => "support-nl@example.com",
"overrides" => []
}
}
}
end
Expand Down