Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
elixir 1.18.3-otp-27
erlang 27.3.3
elixir 1.19.5-otp-28
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotta add Elixir 1.19 to the .github/workflows/ci.yml file to keep track of them

erlang 28.3.1
2 changes: 1 addition & 1 deletion lib/posthog/api/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ defmodule PostHog.API.Client do
@impl __MODULE__
def client(api_key, api_host) do
client =
Req.new(base_url: api_host)
Req.new(base_url: api_host, retry: :transient)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this was changed here we need a new patch release, can you create a sampo changeset like you did in your previous PR (install sampo and run sampo add)

|> Req.Request.put_private(:api_key, api_key)

%__MODULE__{client: client, module: __MODULE__}
Expand Down
20 changes: 20 additions & 0 deletions sdk_compliance_adapter/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where third-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez
4 changes: 2 additions & 2 deletions sdk_compliance_adapter/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Build stage - use Debian for better compatibility
FROM elixir:1.17 AS builder
FROM hexpm/elixir:1.19.5-erlang-28.3.1-debian-bookworm-20260112-slim AS builder

WORKDIR /app

Expand All @@ -21,7 +21,7 @@ COPY sdk_compliance_adapter/config ./sdk_compliance_adapter/config

# Fetch and compile dependencies for the adapter
WORKDIR /app/sdk_compliance_adapter
RUN mix deps.get --only prod
RUN mix deps.get --only $MIX_ENV
RUN mix compile

# Build release
Expand Down
4 changes: 0 additions & 4 deletions sdk_compliance_adapter/lib/sdk_compliance_adapter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,4 @@ defmodule SdkComplianceAdapter do
This adapter wraps the posthog-elixir SDK for compliance testing with the
PostHog SDK Test Harness.
"""

@version "1.0.0"

def version, do: @version
end
34 changes: 11 additions & 23 deletions sdk_compliance_adapter/lib/sdk_compliance_adapter/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ defmodule SdkComplianceAdapter.Router do

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

plug(Plug.Logger)
plug(:match)
plug(Plug.Parsers,
parsers: [:json],
json_decoder: Jason,
json_decoder: JSON,
pass: ["*/*"]
)
plug(:dispatch)
Expand All @@ -27,15 +28,15 @@ defmodule SdkComplianceAdapter.Router do

conn
|> put_resp_content_type("application/json")
|> send_resp(500, Jason.encode!(%{success: false, error: inspect(reason)}))
|> send_resp(500, JSON.encode!(%{success: false, error: inspect(reason)}))
end

# GET /health - Health check endpoint
get "/health" do
response = %{
sdk_name: "posthog-elixir",
sdk_version: @sdk_version,
adapter_version: SdkComplianceAdapter.version()
adapter_version: @adapter_version
}

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

# Start PostHog with the new configuration
case start_posthog(config) do
{:ok, _pid} ->
json_response(conn, 200, %{success: true})

{:error, reason} ->
json_response(conn, 500, %{success: false, error: inspect(reason)})
end
{:ok, _pid} = start_posthog(config)
json_response(conn, 200, %{success: true})
end

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

# POST /reset - Reset SDK state
post "/reset" do
try do
stop_posthog()
SdkComplianceAdapter.State.reset()
json_response(conn, 200, %{success: true})
rescue
e ->
json_response(conn, 500, %{success: false, error: Exception.message(e)})
end
stop_posthog()
SdkComplianceAdapter.State.reset()
json_response(conn, 200, %{success: true})
end

match _ do
Expand All @@ -143,7 +134,7 @@ defmodule SdkComplianceAdapter.Router do
defp json_response(conn, status, data) do
conn
|> put_resp_content_type("application/json")
|> send_resp(status, Jason.encode!(data))
|> send_resp(status, JSON.encode!(data))
end

defp build_config(params) do
Expand Down Expand Up @@ -195,15 +186,12 @@ defmodule SdkComplianceAdapter.Router do
case Process.whereis(SdkComplianceAdapter.PostHog) do
nil ->
:ok

pid ->
# terminate_child can return :ok or {:error, :not_found}
# We don't care about the result - just try to stop it
_ = DynamicSupervisor.terminate_child(SdkComplianceAdapter.DynamicSupervisor, pid)
:ok
end
rescue
# Catch any errors during termination
_ -> :ok
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -9,75 +9,43 @@ defmodule SdkComplianceAdapter.TrackedClient do

@impl true
def client(api_key, api_host) do
# Create the underlying Req client
client =
Req.new(base_url: api_host)
|> Req.Request.put_private(:api_key, api_key)

# Return the standard PostHog.API.Client struct with our module
%PostHog.API.Client{client: client, module: __MODULE__}
client = PostHog.API.Client.client(api_key, api_host)
instrumented_client =
client.client
|> Req.Request.append_response_steps(track: &track/1)
|> Req.Request.append_error_steps(track_error: &track_error/1)
%{client | client: instrumented_client}
end

@impl true
def request(client, method, url, opts) do
# Build the request
req =
client
|> Req.merge(method: method, url: url)
|> Req.merge(opts)
|> then(fn req ->
req
|> Req.Request.fetch_option(:json)
|> case do
{:ok, json} ->
api_key = Req.Request.get_private(req, :api_key)
Req.merge(req, json: Map.put_new(json, :api_key, api_key))

:error ->
req
end
end)

# Extract UUIDs from the batch before sending
uuid_list = extract_uuids(opts)
event_count = count_events(opts)

# Make the request
result = Req.request(req)

# Track the request
case result do
{:ok, %{status: status}} ->
SdkComplianceAdapter.State.record_request(status, event_count, uuid_list)

{:error, reason} ->
SdkComplianceAdapter.State.set_last_error(inspect(reason))
end

result
defdelegate request(client, method, url, opts), to: PostHog.API.Client

def track({request, response}) do
req_body =
request.body
|> to_string()
|> JSON.decode!()

uuid_list = extract_uuids(req_body)
event_count = count_events(req_body)

SdkComplianceAdapter.State.record_request(response.status, event_count, uuid_list)

{request, response}
end

defp extract_uuids(opts) do
case Keyword.get(opts, :json) do
%{batch: batch} when is_list(batch) ->
Enum.map(batch, fn event ->
# Check event level first, then properties
Map.get(event, :uuid) ||
Map.get(event, "uuid") ||
get_in(event, [:properties, :uuid]) ||
get_in(event, ["properties", "uuid"])
end)
|> Enum.reject(&is_nil/1)

_ ->
[]
end

def track_error({request, exception}) do
SdkComplianceAdapter.State.set_last_error(inspect(exception))
{request, exception}
end

defp count_events(opts) do
case Keyword.get(opts, :json) do
%{batch: batch} when is_list(batch) -> length(batch)
_ -> 0
end
defp extract_uuids(request) do
request
|> get_in([Access.key("batch", []), Access.all(), "uuid"])
|> Enum.reject(&is_nil/1)
end

defp count_events(%{"batch" => events}) when is_list(events), do: length(events)
defp count_events(_), do: 0
end
1 change: 0 additions & 1 deletion sdk_compliance_adapter/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ defmodule SdkComplianceAdapter.MixProject do
defp deps do
[
{:plug_cowboy, "~> 2.7"},
{:jason, "~> 1.4"},
{:posthog, path: ".."}
]
end
Expand Down
21 changes: 21 additions & 0 deletions sdk_compliance_adapter/mix.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
%{
"cowboy": {:hex, :cowboy, "2.14.2", "4008be1df6ade45e4f2a4e9e2d22b36d0b5aba4e20b0a0d7049e28d124e34847", [:make, :rebar3], [{:cowlib, ">= 2.16.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "569081da046e7b41b5df36aa359be71a0c8874e5b9cff6f747073fc57baf1ab9"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.16.0", "54592074ebbbb92ee4746c8a8846e5605052f29309d3a873468d76cdf932076f", [:make, :rebar3], [], "hexpm", "7f478d80d66b747344f0ea7708c187645cfcc08b11aa424632f78e25bf05db51"},
"finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [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", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"},
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"logger_json": {:hex, :logger_json, "7.0.4", "e315f2b9a755504658a745f3eab90d88d2cd7ac2ecfd08c8da94d8893965ab5c", [:mix], [{:decimal, ">= 0.0.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:ecto, "~> 3.11", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "d1369f8094e372db45d50672c3b91e8888bcd695fdc444a37a0734e96717c45c"},
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
"mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_ownership": {:hex, :nimble_ownership, "1.0.2", "fa8a6f2d8c592ad4d79b2ca617473c6aefd5869abfa02563a77682038bf916cf", [:mix], [], "hexpm", "098af64e1f6f8609c6672127cfe9e9590a5d3fcdd82bc17a377b8692fd81a879"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"},
"plug_cowboy": {:hex, :plug_cowboy, "2.8.0", "07789e9c03539ee51bb14a07839cc95aa96999fd8846ebfd28c97f0b50c7b612", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9cbfaaf17463334ca31aed38ea7e08a68ee37cabc077b1e9be6d2fb68e0171d0"},
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
"ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
"req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"uuid_v7": {:hex, :uuid_v7, "0.6.0", "1d65727ade8ca619ed40fdef90c4186b50c84657d2b412f7cb79777ab2d47559", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "1dc401134e61da847a7b2a3b28d2593893f457b9f2704893b1ba3ff7946ce91f"},
}
Loading