Skip to content

Commit a9865cd

Browse files
authored
chore: Document public APIs in ExDoc (#120)
1 parent a9327dd commit a9865cd

13 files changed

Lines changed: 226 additions & 92 deletions

File tree

README.md

Lines changed: 57 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -113,91 +113,89 @@ You can always inspect the context:
113113
iex> PostHog.get_context()
114114
%{distinct_id: "distinct_id_of_the_user"}
115115
iex> PostHog.get_event_context("sensitive_event")
116-
%{distinct_id: "distinct_id_of_the_user", "$process_person_profile": true}
116+
%{distinct_id: "distinct_id_of_the_user", "$process_person_profile": false}
117117
```
118118

119119
## Feature Flags
120120

121-
`PostHog.FeatureFlags.check/2` is the main function for checking a feature flag.
121+
Evaluate feature flags once for a user with `PostHog.FeatureFlags.evaluate_flags/1`,
122+
then read values from the returned snapshot.
122123

123124
```elixir
124-
# Simple boolean feature flag
125-
iex> PostHog.FeatureFlags.check("example-feature-flag-1", "user123")
126-
{:ok, true}
127-
128-
# Note how it automatically sets `$feature/example-feature-flag-1` property in the context
129-
iex> PostHog.get_context()
130-
%{"$feature/example-feature-flag-1" => true}
131-
132-
# It will attempt to take distinct_id from the context if it's not provided
133-
iex> PostHog.set_context(%{distinct_id: "user123"})
134-
:ok
135-
iex> PostHog.FeatureFlags.check("example-feature-flag-1")
136-
{:ok, true}
137-
138-
# You can also pass a map with body parameters that will be sent to the /flags API as-is
139-
iex> PostHog.FeatureFlags.check("example-feature-flag-1", %{distinct_id: "user123", groups: %{group_type: "group_id"}})
140-
{:ok, true}
125+
# Boolean feature flag
126+
{:ok, snapshot} = PostHog.FeatureFlags.evaluate_flags("user123")
127+
if PostHog.FeatureFlags.Evaluations.enabled?(snapshot, "new-dashboard") do
128+
# Do something differently for this user
129+
end
141130

142-
# It returns variant if it's set
143-
iex> PostHog.FeatureFlags.check("example-feature-flag-2", "user123")
144-
{:ok, "variant2"}
131+
# Multivariate feature flag
132+
case PostHog.FeatureFlags.Evaluations.get_flag(snapshot, "checkout-flow") do
133+
"variant-a" -> :variant_a
134+
true -> :enabled_boolean_flag
135+
false -> :disabled
136+
nil -> :not_returned
137+
end
145138

146-
# Returns error if feature flag doesn't exist
147-
iex> PostHog.FeatureFlags.check("example-feature-flag-3", "user123")
148-
{:error, %PostHog.UnexpectedResponseError{message: "Feature flag example-feature-flag-3 was not found in the response", response: ...}}
139+
# Optional payload
140+
payload = PostHog.FeatureFlags.Evaluations.get_flag_payload(snapshot, "checkout-flow")
149141
```
150142

151-
If you're feeling adventurous and/or simply writing a script, you can use the `PostHog.FeatureFlags.check!/2` helper instead and it will return a boolean or raise an error.
152-
153-
```elixir
154-
# Simple boolean feature flag
155-
iex> PostHog.FeatureFlags.check!("example-feature-flag-1", "user123")
156-
true
157-
158-
# Works for variants too
159-
iex> PostHog.FeatureFlags.check!("example-feature-flag-2", "user123")
160-
"variant2"
161-
162-
# Raises error if feature flag doesn't exist
163-
iex> PostHog.FeatureFlags.check!("example-feature-flag-3", "user123")
164-
** (PostHog.UnexpectedResponseError) Feature flag example-feature-flag-3 was not found in the response
165-
```
143+
`get_flag/2` returns the variant string for multivariate flags, `true` for enabled
144+
boolean flags, `false` for disabled flags, and `nil` when the flag was not returned
145+
by the evaluation.
166146

167-
### Getting the Full Flag Result
147+
### Include feature flag information when capturing events
168148

169-
If you need more than just the value -- for example, the payload configured for a
170-
flag or variant -- use `PostHog.FeatureFlags.get_feature_flag_result/2`:
149+
If you want to break down or filter captured events by feature flag value, put the
150+
same snapshot in the process context before capturing events:
171151

172152
```elixir
173-
iex> PostHog.FeatureFlags.get_feature_flag_result("my-flag", "user123")
174-
{:ok, %PostHog.FeatureFlags.Result{key: "my-flag", enabled: true, variant: nil, payload: nil}}
153+
{:ok, snapshot} = PostHog.FeatureFlags.evaluate_flags("user123")
175154

176-
# Multivariant flag with a JSON payload
177-
iex> PostHog.FeatureFlags.get_feature_flag_result("my-experiment", "user123")
178-
{:ok, %PostHog.FeatureFlags.Result{key: "my-experiment", enabled: true, variant: "control", payload: %{"button_color" => "blue"}}}
155+
if PostHog.FeatureFlags.Evaluations.enabled?(snapshot, "new-dashboard") do
156+
# Do something differently for this user
157+
end
179158

180-
# Flag not found
181-
iex> PostHog.FeatureFlags.get_feature_flag_result("non-existent-flag", "user123")
182-
{:ok, nil}
159+
PostHog.FeatureFlags.set_in_context(snapshot)
160+
PostHog.capture("page_viewed", %{distinct_id: "user123"})
183161
```
184162

185-
By default this sends a `$feature_flag_called` event, which PostHog uses to
186-
track feature flag usage in your analytics, and to measure experiment exposure
187-
when the flag is linked to an A/B test. You can opt out with `send_event: false`:
163+
This attaches `$feature/<flag-key>` properties and `$active_feature_flags` without
164+
making another `/flags` request. To reduce event property bloat, filter the
165+
snapshot first:
188166

189167
```elixir
190-
iex> PostHog.FeatureFlags.get_feature_flag_result("my-flag", "user123", send_event: false)
191-
{:ok, %PostHog.FeatureFlags.Result{key: "my-flag", enabled: true, variant: nil, payload: nil}}
168+
# Attach only flags accessed with enabled?/2 or get_flag/2
169+
PostHog.FeatureFlags.set_in_context(
170+
PostHog.FeatureFlags.Evaluations.only_accessed(snapshot)
171+
)
172+
173+
# Or attach only specific flags
174+
PostHog.FeatureFlags.set_in_context(
175+
PostHog.FeatureFlags.Evaluations.only(snapshot, ["checkout-flow", "new-dashboard"])
176+
)
192177
```
193178

194-
A bang variant is also available:
179+
### Evaluating only specific flags
180+
181+
By default, `evaluate_flags/1` evaluates every flag for the user. If you only need
182+
a few flags, pass `:flag_keys` to request only those flags:
195183

196184
```elixir
197-
iex> PostHog.FeatureFlags.get_feature_flag_result!("my-flag", "user123")
198-
%PostHog.FeatureFlags.Result{key: "my-flag", enabled: true, variant: nil, payload: nil}
185+
{:ok, snapshot} =
186+
PostHog.FeatureFlags.evaluate_flags(%{
187+
distinct_id: "user123",
188+
flag_keys: ["checkout-flow", "new-dashboard"]
189+
})
199190
```
200191

192+
> #### Deprecated feature flag helpers {: .warning}
193+
>
194+
> `PostHog.FeatureFlags.check/2`, `PostHog.FeatureFlags.check!/2`,
195+
> `PostHog.FeatureFlags.get_feature_flag_result/2`, and
196+
> `PostHog.FeatureFlags.get_feature_flag_result!/2` still work during the
197+
> migration period, but prefer `evaluate_flags/1` for new code.
198+
201199
## Error Tracking
202200

203201
Error Tracking is enabled by default.

lib/posthog.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ defmodule PostHog do
99
@typedoc ~S(Event name, such as `"user_signed_up"` or `"$create_alias"`)
1010
@type event() :: String.t()
1111

12-
@typedoc "string representing distinct ID"
12+
@typedoc "String representing a PostHog distinct ID."
1313
@type distinct_id() :: String.t()
1414

1515
@typedoc """
@@ -194,6 +194,7 @@ defmodule PostHog do
194194
> PostHog.get_event_context(MyPostHog, "$exception")
195195
%{foo: "bar"}
196196
"""
197-
@spec get_event_context(supervisor_name()) :: properties()
197+
@spec get_event_context(event()) :: properties()
198+
@spec get_event_context(supervisor_name(), event()) :: properties()
198199
def get_event_context(name \\ __MODULE__, event), do: PostHog.Context.get(name, event)
199200
end

lib/posthog/api/client.ex

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,16 +98,31 @@ defmodule PostHog.API.Client do
9898

9999
defstruct [:client, :module]
100100

101+
@typedoc """
102+
Wrapper returned by `c:client/2` and stored in `t:PostHog.Config.config/0`.
103+
104+
- `:client` - opaque client state passed back to `c:request/4`.
105+
- `:module` - module implementing this behaviour.
106+
"""
101107
@type t() :: %__MODULE__{
102108
client: client(),
103109
module: atom()
104110
}
111+
105112
@typedoc """
106113
Arbitrary term that is passed as the first argument to the `c:request/4` callback.
107114
108115
For the default client, this is a `t:Req.Request.t/0` struct.
109116
"""
110117
@type client() :: any()
118+
119+
@typedoc """
120+
Response tuple returned by `c:request/4`.
121+
122+
Successful responses must expose at least a numeric `:status` and decoded
123+
`:body`; errors should return the exception or error struct from the HTTP
124+
client.
125+
"""
111126
@type response() :: {:ok, %{status: non_neg_integer(), body: any()}} | {:error, Exception.t()}
112127

113128
@doc """

lib/posthog/config.ex

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ defmodule PostHog.Config do
77
test_mode: [
88
type: :boolean,
99
default: false,
10-
doc: "Test mode allows tests assert captured events."
10+
doc:
11+
"Test mode keeps captured events in memory for assertions instead of sending them to PostHog."
1112
]
1213
]
1314

@@ -134,12 +135,21 @@ defmodule PostHog.Config do
134135
"""
135136

136137
@typedoc """
137-
Map containing valid configuration.
138+
Map containing validated configuration for a PostHog supervision tree.
138139
139-
It mostly follows `t:options/0`, but the internal structure shouldn't be relied upon.
140+
It mostly follows `t:options/0`, but also includes runtime values such as the
141+
initialized API client, resolved in-app modules, and system global properties.
142+
The internal structure should not be relied upon outside of starting
143+
`PostHog.Supervisor` or reading values through `PostHog.config/1`.
140144
"""
141145
@opaque config() :: map()
142146

147+
@typedoc """
148+
Keyword options accepted by `validate/1` and `validate!/1`.
149+
150+
See the module documentation for the full schema, defaults, and remarks for
151+
each configuration option.
152+
"""
143153
@type options() :: unquote(NimbleOptions.option_typespec(@compiled_configuration_schema))
144154

145155
@doc false
@@ -166,7 +176,9 @@ defmodule PostHog.Config do
166176
end
167177

168178
@doc """
169-
See `validate/1`.
179+
Validates configuration and returns a `t:config/0`, raising if validation fails.
180+
181+
See `validate/1` for the accepted options and return shape.
170182
"""
171183
@spec validate!(options()) :: config()
172184
def validate!(options) do
@@ -175,7 +187,21 @@ defmodule PostHog.Config do
175187
end
176188

177189
@doc """
178-
Validates configuration against the schema.
190+
Validates configuration against the supervisor schema.
191+
192+
## Parameters
193+
194+
- `options` - keyword list matching `t:options/0`.
195+
196+
## Returns
197+
198+
Returns `{:ok, config}` with a normalized `t:config/0` on success, or
199+
`{:error, %NimbleOptions.ValidationError{}}` when the options are invalid.
200+
201+
## Remarks
202+
203+
String `:api_key` and `:api_host` values are trimmed before validation. A blank
204+
`:api_host` falls back to the default PostHog US ingestion host.
179205
"""
180206
@spec validate(options()) ::
181207
{:ok, config()} | {:error, NimbleOptions.ValidationError.t()}

lib/posthog/error.ex

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
11
defmodule PostHog.Error do
22
@moduledoc """
3-
PostHog error
3+
Generic PostHog SDK error.
4+
5+
## Fields
6+
7+
- `:message` - human-readable error message.
48
"""
59

10+
@typedoc "Exception raised for SDK errors that are not tied to an HTTP response."
611
@type t() :: %__MODULE__{message: String.t()}
712

813
defexception [:message]
914
end
1015

1116
defmodule PostHog.UnexpectedResponseError do
1217
@moduledoc """
13-
PostHog error that includes a reponse from the API, either full or partial.
18+
PostHog error that includes a response from the API, either full or partial.
19+
20+
## Fields
21+
22+
- `:message` - human-readable error message.
23+
- `:response` - API response data that caused the error.
1424
"""
25+
26+
@typedoc "Exception raised when PostHog returns a response the SDK cannot handle."
1527
@type t() :: %__MODULE__{response: any(), message: String.t()}
1628

1729
defexception [:response, :message]

lib/posthog/feature_flags.ex

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ defmodule PostHog.FeatureFlags do
6666
6767
Get all feature flags with full request body:
6868
69-
PostHog.FeatureFlags.flags_for(%{distinct_id: "user123", group: %{group_type: "group_id"}})
69+
PostHog.FeatureFlags.flags_for(%{distinct_id: "user123", groups: %{group_type: "group_id"}})
7070
7171
Get all feature flags for `distinct_id` from the context:
7272
@@ -164,21 +164,27 @@ defmodule PostHog.FeatureFlags do
164164
end
165165
end
166166

167-
@doc false
168-
def set_in_context(%__MODULE__.Evaluations{} = snapshot),
169-
do: set_in_context(PostHog, snapshot)
170-
171167
@doc """
172168
Copies a snapshot's `$feature/<key>` and `$active_feature_flags` properties
173-
into the per-process PostHog context.
169+
into the default per-process PostHog context.
170+
171+
## Parameters
172+
173+
- `snapshot` - `t:PostHog.FeatureFlags.Evaluations.t/0` returned by
174+
`evaluate_flags/2` or one of the filtering helpers.
175+
176+
## Returns
177+
178+
Returns `:ok`.
179+
180+
## Remarks
174181
175182
Any subsequent `PostHog.capture/3` from this process automatically attaches
176183
these properties to the captured event — no additional `/flags` request,
177184
with the values guaranteed to match what the snapshot already evaluated.
178185
179-
This is the idiomatic Elixir way to enrich captured events from an
180-
`evaluate_flags/2` snapshot. For one-off enrichment without touching context,
181-
merge `PostHog.FeatureFlags.Evaluations.event_properties/1` into a capture's
186+
For one-off enrichment without touching context, merge
187+
`PostHog.FeatureFlags.Evaluations.event_properties/1` into a capture's
182188
properties directly.
183189
184190
## Examples
@@ -189,6 +195,29 @@ defmodule PostHog.FeatureFlags do
189195
# All subsequent captures pick up $feature/* and $active_feature_flags
190196
PostHog.capture("page_viewed", %{distinct_id: "user123"})
191197
"""
198+
@spec set_in_context(__MODULE__.Evaluations.t()) :: :ok
199+
def set_in_context(%__MODULE__.Evaluations{} = snapshot),
200+
do: set_in_context(PostHog, snapshot)
201+
202+
@doc """
203+
Copies a snapshot's `$feature/<key>` and `$active_feature_flags` properties
204+
into a named PostHog instance's per-process context.
205+
206+
## Parameters
207+
208+
- `name` - supervisor name of the PostHog instance whose context should be
209+
updated.
210+
- `snapshot` - `t:PostHog.FeatureFlags.Evaluations.t/0` returned by
211+
`evaluate_flags/2` or one of the filtering helpers.
212+
213+
## Returns
214+
215+
Returns `:ok`.
216+
217+
## Remarks
218+
219+
This is the named-instance variant of `set_in_context/1`.
220+
"""
192221
@spec set_in_context(PostHog.supervisor_name(), __MODULE__.Evaluations.t()) :: :ok
193222
def set_in_context(name, %__MODULE__.Evaluations{} = snapshot) when is_atom(name) do
194223
PostHog.set_context(name, __MODULE__.Evaluations.event_properties(snapshot))
@@ -245,7 +274,7 @@ defmodule PostHog.FeatureFlags do
245274
246275
Check boolean feature flag through a named PostHog instance:
247276
248-
PostHog.check(MyPostHog, "example-feature-flag-1", "user123")
277+
PostHog.FeatureFlags.check(MyPostHog, "example-feature-flag-1", "user123")
249278
"""
250279
@spec check(PostHog.supervisor_name(), String.t(), PostHog.distinct_id() | map() | nil) ::
251280
{:ok, boolean()} | {:ok, String.t()} | {:error, Exception.t()}

lib/posthog/feature_flags/evaluations.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ defmodule PostHog.FeatureFlags.Evaluations do
229229
properties = PostHog.FeatureFlags.Evaluations.event_properties(snapshot)
230230
PostHog.capture("page_viewed", Map.merge(%{distinct_id: "u1"}, properties))
231231
"""
232-
@spec event_properties(t()) :: %{String.t() => any()}
232+
@spec event_properties(t()) :: PostHog.properties()
233233
def event_properties(%__MODULE__{flags: flags}) do
234234
{properties, active} =
235235
Enum.reduce(flags, {%{}, []}, fn {key, %Result{} = result}, {props, active} ->

0 commit comments

Comments
 (0)