Skip to content

Commit 066d4fd

Browse files
Merge pull request #79 from martosaur/am-compliance-adapter-polish
Some polish for sdk compliance
2 parents a444e6c + 32d662d commit 066d4fd

File tree

12 files changed

+108
-101
lines changed

12 files changed

+108
-101
lines changed

.github/workflows/ci.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,18 @@ jobs:
1515
# See https://hexdocs.pm/elixir/compatibility-and-deprecations.html#between-elixir-and-erlang-otp
1616
strategy:
1717
matrix:
18-
elixir: ["1.17.x", "1.18.x"]
18+
elixir: ["1.17.x", "1.18.x", "1.19.x"]
1919
otp: ["25.x", "26.x", "27.x", "28.x"]
2020
exclude:
2121
# Elixir 1.17 doesn't support OTP 28
2222
- elixir: "1.17.x"
2323
otp: "28.x"
24+
# Elixir 1.18 doesn't support OTP 28
25+
- elixir: "1.18.x"
26+
otp: "28.x"
27+
# Elixir 1.19 doesn't support OTP 25
28+
- elixir: "1.19.x"
29+
otp: "25.x"
2430

2531
steps:
2632
- uses: actions/checkout@v3
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
hex/posthog: minor
3+
---
4+
5+
Implement proper retry behavior for requests. Also respects X-Retry-Later header.

.tool-versions

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
elixir 1.18.3-otp-27
2-
erlang 27.3.3
1+
elixir 1.19.5-otp-28
2+
erlang 28.3.1

lib/posthog/api/client.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ defmodule PostHog.API.Client do
4848
@impl __MODULE__
4949
def client(api_key, api_host) do
5050
client =
51-
Req.new(base_url: api_host)
51+
Req.new(base_url: api_host, retry: :transient)
5252
|> Req.Request.put_private(:api_key, api_key)
5353

5454
%__MODULE__{client: client, module: __MODULE__}

lib/posthog/handler.ex

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ defmodule PostHog.Handler do
22
@moduledoc """
33
A [`logger handler`](https://www.erlang.org/doc/apps/kernel/logger_chapter.html#handlers).
44
"""
5-
@behaviour :logger_handler
5+
if System.otp_release() |> String.to_integer() >= 27 do
6+
@behaviour :logger_handler
7+
end
68

79
alias PostHog.Context
810

9-
@impl :logger_handler
11+
# credo:disable-for-next-line Credo.Check.Design.TagTODO
12+
# TODO: add @impl :logger_handler once we drop support for OTP < 27
13+
@doc false
1014
def log(log_event, %{config: config}) do
1115
maybe_properties =
1216
cond do

sdk_compliance_adapter/.gitignore

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# The directory Mix will write compiled artifacts to.
2+
/_build/
3+
4+
# If you run "mix test --cover", coverage assets end up here.
5+
/cover/
6+
7+
# The directory Mix downloads your dependencies sources to.
8+
/deps/
9+
10+
# Where third-party dependencies like ExDoc output generated docs.
11+
/doc/
12+
13+
# Ignore .fetch files in case you like to edit your project deps locally.
14+
/.fetch
15+
16+
# If the VM crashes, it generates a dump, let's ignore it too.
17+
erl_crash.dump
18+
19+
# Also ignore archive artifacts (built via "mix archive.build").
20+
*.ez

sdk_compliance_adapter/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Build stage - use Debian for better compatibility
2-
FROM elixir:1.17 AS builder
2+
FROM hexpm/elixir:1.19.5-erlang-28.3.1-debian-bookworm-20260112-slim AS builder
33

44
WORKDIR /app
55

@@ -21,7 +21,7 @@ COPY sdk_compliance_adapter/config ./sdk_compliance_adapter/config
2121

2222
# Fetch and compile dependencies for the adapter
2323
WORKDIR /app/sdk_compliance_adapter
24-
RUN mix deps.get --only prod
24+
RUN mix deps.get --only $MIX_ENV
2525
RUN mix compile
2626

2727
# Build release

sdk_compliance_adapter/lib/sdk_compliance_adapter.ex

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,4 @@ defmodule SdkComplianceAdapter do
55
This adapter wraps the posthog-elixir SDK for compliance testing with the
66
PostHog SDK Test Harness.
77
"""
8-
9-
@version "1.0.0"
10-
11-
def version, do: @version
128
end

sdk_compliance_adapter/lib/sdk_compliance_adapter/router.ex

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ defmodule SdkComplianceAdapter.Router do
1111

1212
# Capture SDK version at compile time since Mix isn't available at runtime
1313
@sdk_version Application.spec(:posthog, :vsn) |> to_string()
14+
@adapter_version Application.spec(:sdk_compliance_adapter, :vsn) |> to_string()
1415

1516
plug(Plug.Logger)
1617
plug(:match)
1718
plug(Plug.Parsers,
1819
parsers: [:json],
19-
json_decoder: Jason,
20+
json_decoder: JSON,
2021
pass: ["*/*"]
2122
)
2223
plug(:dispatch)
@@ -27,15 +28,15 @@ defmodule SdkComplianceAdapter.Router do
2728

2829
conn
2930
|> put_resp_content_type("application/json")
30-
|> send_resp(500, Jason.encode!(%{success: false, error: inspect(reason)}))
31+
|> send_resp(500, JSON.encode!(%{success: false, error: inspect(reason)}))
3132
end
3233

3334
# GET /health - Health check endpoint
3435
get "/health" do
3536
response = %{
3637
sdk_name: "posthog-elixir",
3738
sdk_version: @sdk_version,
38-
adapter_version: SdkComplianceAdapter.version()
39+
adapter_version: @adapter_version
3940
}
4041

4142
json_response(conn, 200, response)
@@ -56,13 +57,8 @@ defmodule SdkComplianceAdapter.Router do
5657
SdkComplianceAdapter.State.set_config(config)
5758

5859
# Start PostHog with the new configuration
59-
case start_posthog(config) do
60-
{:ok, _pid} ->
61-
json_response(conn, 200, %{success: true})
62-
63-
{:error, reason} ->
64-
json_response(conn, 500, %{success: false, error: inspect(reason)})
65-
end
60+
{:ok, _pid} = start_posthog(config)
61+
json_response(conn, 200, %{success: true})
6662
end
6763

6864
# POST /capture - Capture a single event
@@ -124,14 +120,9 @@ defmodule SdkComplianceAdapter.Router do
124120

125121
# POST /reset - Reset SDK state
126122
post "/reset" do
127-
try do
128-
stop_posthog()
129-
SdkComplianceAdapter.State.reset()
130-
json_response(conn, 200, %{success: true})
131-
rescue
132-
e ->
133-
json_response(conn, 500, %{success: false, error: Exception.message(e)})
134-
end
123+
stop_posthog()
124+
SdkComplianceAdapter.State.reset()
125+
json_response(conn, 200, %{success: true})
135126
end
136127

137128
match _ do
@@ -143,7 +134,7 @@ defmodule SdkComplianceAdapter.Router do
143134
defp json_response(conn, status, data) do
144135
conn
145136
|> put_resp_content_type("application/json")
146-
|> send_resp(status, Jason.encode!(data))
137+
|> send_resp(status, JSON.encode!(data))
147138
end
148139

149140
defp build_config(params) do
@@ -195,15 +186,12 @@ defmodule SdkComplianceAdapter.Router do
195186
case Process.whereis(SdkComplianceAdapter.PostHog) do
196187
nil ->
197188
:ok
198-
189+
199190
pid ->
200191
# terminate_child can return :ok or {:error, :not_found}
201192
# We don't care about the result - just try to stop it
202193
_ = DynamicSupervisor.terminate_child(SdkComplianceAdapter.DynamicSupervisor, pid)
203194
:ok
204195
end
205-
rescue
206-
# Catch any errors during termination
207-
_ -> :ok
208196
end
209197
end

sdk_compliance_adapter/lib/sdk_compliance_adapter/tracked_client.ex

Lines changed: 33 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -9,75 +9,43 @@ defmodule SdkComplianceAdapter.TrackedClient do
99

1010
@impl true
1111
def client(api_key, api_host) do
12-
# Create the underlying Req client
13-
client =
14-
Req.new(base_url: api_host)
15-
|> Req.Request.put_private(:api_key, api_key)
16-
17-
# Return the standard PostHog.API.Client struct with our module
18-
%PostHog.API.Client{client: client, module: __MODULE__}
12+
client = PostHog.API.Client.client(api_key, api_host)
13+
instrumented_client =
14+
client.client
15+
|> Req.Request.append_response_steps(track: &track/1)
16+
|> Req.Request.append_error_steps(track_error: &track_error/1)
17+
18+
%{client | client: instrumented_client}
1919
end
20-
20+
2121
@impl true
22-
def request(client, method, url, opts) do
23-
# Build the request
24-
req =
25-
client
26-
|> Req.merge(method: method, url: url)
27-
|> Req.merge(opts)
28-
|> then(fn req ->
29-
req
30-
|> Req.Request.fetch_option(:json)
31-
|> case do
32-
{:ok, json} ->
33-
api_key = Req.Request.get_private(req, :api_key)
34-
Req.merge(req, json: Map.put_new(json, :api_key, api_key))
35-
36-
:error ->
37-
req
38-
end
39-
end)
40-
41-
# Extract UUIDs from the batch before sending
42-
uuid_list = extract_uuids(opts)
43-
event_count = count_events(opts)
44-
45-
# Make the request
46-
result = Req.request(req)
47-
48-
# Track the request
49-
case result do
50-
{:ok, %{status: status}} ->
51-
SdkComplianceAdapter.State.record_request(status, event_count, uuid_list)
52-
53-
{:error, reason} ->
54-
SdkComplianceAdapter.State.set_last_error(inspect(reason))
55-
end
56-
57-
result
22+
defdelegate request(client, method, url, opts), to: PostHog.API.Client
23+
24+
def track({request, response}) do
25+
req_body =
26+
request.body
27+
|> to_string()
28+
|> JSON.decode!()
29+
30+
uuid_list = extract_uuids(req_body)
31+
event_count = count_events(req_body)
32+
33+
SdkComplianceAdapter.State.record_request(response.status, event_count, uuid_list)
34+
35+
{request, response}
5836
end
59-
60-
defp extract_uuids(opts) do
61-
case Keyword.get(opts, :json) do
62-
%{batch: batch} when is_list(batch) ->
63-
Enum.map(batch, fn event ->
64-
# Check event level first, then properties
65-
Map.get(event, :uuid) ||
66-
Map.get(event, "uuid") ||
67-
get_in(event, [:properties, :uuid]) ||
68-
get_in(event, ["properties", "uuid"])
69-
end)
70-
|> Enum.reject(&is_nil/1)
71-
72-
_ ->
73-
[]
74-
end
37+
38+
def track_error({request, exception}) do
39+
SdkComplianceAdapter.State.set_last_error(inspect(exception))
40+
{request, exception}
7541
end
7642

77-
defp count_events(opts) do
78-
case Keyword.get(opts, :json) do
79-
%{batch: batch} when is_list(batch) -> length(batch)
80-
_ -> 0
81-
end
43+
defp extract_uuids(request) do
44+
request
45+
|> get_in([Access.key("batch", []), Access.all(), "uuid"])
46+
|> Enum.reject(&is_nil/1)
8247
end
48+
49+
defp count_events(%{"batch" => events}) when is_list(events), do: length(events)
50+
defp count_events(_), do: 0
8351
end

0 commit comments

Comments
 (0)