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
144 changes: 0 additions & 144 deletions .circleci/config.yml

This file was deleted.

93 changes: 93 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: CI
on:
push:
branches: [main,master]
pull_request:
branches: [main,master]
jobs:
test:
name: Test (Elixir ${{ matrix.elixir }} | OTP ${{ matrix.otp }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- elixir: 1.18.x
otp: 27
os: ubuntu-22.04
- elixir: 1.19.x
otp: 28
os: ubuntu-22.04
env:
MIX_ENV: test
steps:
- name: Setup Elixir
uses: erlef/setup-beam@v1
with:
elixir-version: ${{ matrix.elixir }}
otp-version: ${{ matrix.otp }}

- name: Checkout code
uses: actions/checkout@v4

- name: Cache dependencies
uses: actions/cache@v4
id: cache-deps
with:
path: |
deps
_build
key: |
mix-${{ runner.os }}-${{ matrix.elixir }}-${{ matrix.otp }}-${{ hashFiles('**/mix.lock') }}
restore-keys: |
mix-${{ runner.os }}-${{ matrix.elixir }}-${{ matrix.otp }}-
- name: Install dependencies
run: mix deps.get

- name: Compile
run: mix compile

- name: Check for unused packages
run: mix deps.unlock --check-unused

- run: mix format --check-formatted

- run: mix credo --strict

- run: mix dialyzer

- name: Check for abandonded packages
run: mix hex.audit

- name: Check outdated dependencies
run: mix hex.outdated --within-requirements || true

- name: Check for vulnerable packages
run: mix hex.audit

- name: Run tests
run: mix test

- name: Run tests (with coverage)
run: mix test --cover --export-coverage default

- name: Scan for security vulnerabilities
run: mix sobelow --exit --threshold medium

publish:
name: Publish (Dry Run)
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
elixir-version: 1.18
otp-version: 27
- name: Fetch dependencies
run: mix deps.get
- name: Compile
run: mix compile
- name: Publish package
env:
HEX_API_KEY: ${{ secrets.HEX_API_KEY }}
run: mix hex.publish --organization ${{ vars.HEX_ORG }} --dry-run --replace --yes
36 changes: 34 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Zexbox

[![Hex.pm](https://img.shields.io/hexpm/v/zexbox.svg)](https://hex.pm/packages/zexbox)
[![CircleCI](https://dl.circleci.com/status-badge/img/gh/Intellection/zexbox/tree/master.svg?style=shield)](https://dl.circleci.com/status-badge/redirect/gh/Intellection/zexbox/tree/master)
[![CI](https://github.com/Intellection/zexbox/actions/workflows/ci.yml/badge.svg)](https://github.com/Intellection/zexbox/actions/workflows/ci.yml)
[![Documentation](https://img.shields.io/badge/documentation-gray)](https://hexdocs.pm/zexbox/api-reference.html)

## Installation
Expand All @@ -16,6 +16,8 @@ end

## LaunchDarkly Feature Flags

The Zexbox library provides an idiomatic Elixir wrapper around the LaunchDarkly Erlang SDK with support for contexts, multi-contexts, and all modern LaunchDarkly features.

### Configuration

Configuration is fairly simple, with the only required piece of configuration being the `sdk_key`. For production environments we recommend also including `:email` as a private attribute:
Expand Down Expand Up @@ -80,7 +82,35 @@ end

Stopping a client with a custom tag can be done using the `Zexbox.Flags.stop/1` function.

Evaluating a flag can be achieved by simply calling the `Zexbox.Flags.variation/3` function.
### Using Contexts

Contexts are the recommended way to evaluate feature flags. They provide type safety and support for multi-entity targeting:

```elixir
alias Zexbox.Flags.Context

# Simple user context
context = Context.new("user-123")
Zexbox.Flags.variation("my-flag", context, false)

# Context with attributes
context =
Context.new("user-123")
|> Context.set("email", "user@example.com")
|> Context.set("plan", "enterprise")
|> Context.set_private_attributes(["email"])

Zexbox.Flags.variation("premium-feature", context, false)

# Multi-context (target based on user AND organization)
user = Context.new("user-123", "user")
org = Context.new("org-456", "organization")
multi = Context.new_multi([user, org])

Zexbox.Flags.variation("enterprise-feature", multi, false)
```

**Backward Compatibility**: Raw maps are still supported:

```elixir
Zexbox.Flags.variation(
Expand All @@ -90,6 +120,8 @@ Zexbox.Flags.variation(
)
```

For more details, see the [Context Module Guide](CONTEXT_MODULE_GUIDE.md).

## Logging

Default logging can be attached to your controllers by calling `Zexbox.Logging.attach_controller_logs!` in the `start/2` function of your `Application` module:
Expand Down
17 changes: 5 additions & 12 deletions test/zexbox/metrics/client_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ defmodule Zexbox.Metrics.ClientTest do
alias Zexbox.Metrics.{Client, Connection, Series}
alias Zexbox.Metrics.ContextRegistry

setup_all do
ensure_registry_started()
:ok
end

@map %{
measurement: "my_measurement",
fields: %{
Expand All @@ -21,6 +16,11 @@ defmodule Zexbox.Metrics.ClientTest do
}

describe "write_metric/1" do
setup do
start_supervised!(ContextRegistry)
:ok
end

test_with_mock "writes the metric when given a series", Connection,
write: fn metrics -> {:ok, metrics} end do
series = struct(Series, @map)
Expand Down Expand Up @@ -60,11 +60,4 @@ defmodule Zexbox.Metrics.ClientTest do
Zexbox.Metrics.enable_for_process()
end
end

defp ensure_registry_started do
case Process.whereis(ContextRegistry) do
nil -> {:ok, _pid} = ContextRegistry.start_link()
_pid -> :ok
end
end
end
19 changes: 7 additions & 12 deletions test/zexbox/metrics/context_registry_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ defmodule Zexbox.Metrics.ContextRegistryTest do

alias Zexbox.Metrics.ContextRegistry

setup_all do
ensure_registry_started()
:ok
end

describe "register/1, unregister/1, disabled?/1" do
setup do
# Start a supervised ContextRegistry for each test
# This ensures a clean state and proper cleanup
start_supervised!(ContextRegistry)
:ok
end

test "registers and unregisters a pid" do
pid = self()

Expand Down Expand Up @@ -59,11 +61,4 @@ defmodule Zexbox.Metrics.ContextRegistryTest do
eventually(predicate, attempts - 1)
end
end

defp ensure_registry_started do
case Process.whereis(ContextRegistry) do
nil -> {:ok, _pid} = ContextRegistry.start_link()
_pid -> :ok
end
end
end
Loading