diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 851de773d7..51aeedf96c 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -11,7 +11,7 @@ concurrency: cancel-in-progress: true env: - CACHE_VERSION: v16 + CACHE_VERSION: v17 PERSISTENT_CACHE_DIR: cached jobs: diff --git a/.tool-versions b/.tool-versions index 862023bb74..d1ef1f421b 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,3 @@ -erlang 27.3.1 -elixir 1.18.3-otp-27 +erlang 27.3.4.6 +elixir 1.19.4-otp-27 nodejs 23.2.0 diff --git a/Dockerfile b/Dockerfile index a726d05aec..b70727b14e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,10 @@ # we can not use the pre-built tar because the distribution is # platform specific, it makes sense to build it in the docker +ARG ALPINE_VERSION=3.22.2 + #### Builder -FROM hexpm/elixir:1.18.3-erlang-27.3.1-alpine-3.21.3 AS buildcontainer +FROM hexpm/elixir:1.19.4-erlang-27.3.4.6-alpine-${ALPINE_VERSION} AS buildcontainer ARG MIX_ENV=ce @@ -20,7 +22,7 @@ RUN mkdir /app WORKDIR /app # install build dependencies -RUN apk add --no-cache git "nodejs-current=23.2.0-r1" yarn npm python3 ca-certificates wget gnupg make gcc libc-dev brotli +RUN apk add --no-cache git "nodejs-current=23.11.1-r0" yarn npm python3 ca-certificates wget gnupg make gcc libc-dev brotli COPY mix.exs ./ COPY mix.lock ./ @@ -54,7 +56,7 @@ COPY rel rel RUN mix release plausible # Main Docker Image -FROM alpine:3.21.3 +FROM alpine:${ALPINE_VERSION} LABEL maintainer="plausible.io " ARG BUILD_METADATA={} @@ -84,3 +86,4 @@ EXPOSE 8000 ENV DEFAULT_DATA_DIR=/var/lib/plausible VOLUME /var/lib/plausible CMD ["run"] + diff --git a/lib/plausible/auth/user.ex b/lib/plausible/auth/user.ex index 9697a4265d..d5aba33e68 100644 --- a/lib/plausible/auth/user.ex +++ b/lib/plausible/auth/user.ex @@ -1,15 +1,3 @@ -defimpl Bamboo.Formatter, for: Plausible.Auth.User do - def format_email_address(user, _opts) do - {user.name, user.email} - end -end - -defimpl FunWithFlags.Actor, for: Plausible.Auth.User do - def id(%{id: id}) do - "user:#{id}" - end -end - defmodule Plausible.Auth.User do use Plausible use Ecto.Schema @@ -284,3 +272,15 @@ defmodule Plausible.Auth.User do end end end + +defimpl Bamboo.Formatter, for: Plausible.Auth.User do + def format_email_address(user, _opts) do + {user.name, user.email} + end +end + +defimpl FunWithFlags.Actor, for: Plausible.Auth.User do + def id(%{id: id}) do + "user:#{id}" + end +end diff --git a/lib/plausible/google/ga4/api.ex b/lib/plausible/google/ga4/api.ex index b5ebc98bde..2dc4332846 100644 --- a/lib/plausible/google/ga4/api.ex +++ b/lib/plausible/google/ga4/api.ex @@ -202,7 +202,7 @@ defmodule Plausible.Google.GA4.API do end end - defp prepare_request(report_request, date_range, property, access_token) do + defp prepare_request(%GA4.ReportRequest{} = report_request, date_range, property, access_token) do %GA4.ReportRequest{ report_request | date_range: date_range, diff --git a/lib/plausible/shield/country_rule_cache.ex b/lib/plausible/shield/country_rule_cache.ex index 453e0226e5..9ac9563f49 100644 --- a/lib/plausible/shield/country_rule_cache.ex +++ b/lib/plausible/shield/country_rule_cache.ex @@ -44,7 +44,7 @@ defmodule Plausible.Shield.CountryRuleCache do |> where([rule, site], rule.country_code == ^country_code and site.domain == ^domain) case Plausible.Repo.one(query) do - {_, _, rule} -> %CountryRule{rule | from_cache?: false} + {_, _, rule = %CountryRule{}} -> %CountryRule{rule | from_cache?: false} _any -> nil end end diff --git a/lib/plausible/shield/hostname_rule_cache.ex b/lib/plausible/shield/hostname_rule_cache.ex index 14f2d3aa19..e5ce6a067a 100644 --- a/lib/plausible/shield/hostname_rule_cache.ex +++ b/lib/plausible/shield/hostname_rule_cache.ex @@ -45,7 +45,7 @@ defmodule Plausible.Shield.HostnameRuleCache do case Plausible.Repo.all(query) do [_ | _] = results -> - Enum.map(results, fn {_, _, rule} -> + Enum.map(results, fn {_, _, rule = %HostnameRule{}} -> %HostnameRule{rule | from_cache?: false} end) diff --git a/lib/plausible/shield/ip_rule_cache.ex b/lib/plausible/shield/ip_rule_cache.ex index 7644a9477e..43946fcf3f 100644 --- a/lib/plausible/shield/ip_rule_cache.ex +++ b/lib/plausible/shield/ip_rule_cache.ex @@ -44,7 +44,7 @@ defmodule Plausible.Shield.IPRuleCache do |> where([rule, site], rule.inet == ^address and site.domain == ^domain) case Plausible.Repo.one(query) do - {_, _, rule} -> %IPRule{rule | from_cache?: false} + {_, _, rule = %IPRule{}} -> %IPRule{rule | from_cache?: false} _any -> nil end end diff --git a/lib/plausible/shield/page_rule_cache.ex b/lib/plausible/shield/page_rule_cache.ex index e9ba3039d1..4c7dab0d24 100644 --- a/lib/plausible/shield/page_rule_cache.ex +++ b/lib/plausible/shield/page_rule_cache.ex @@ -44,7 +44,7 @@ defmodule Plausible.Shield.PageRuleCache do |> where([..., site], site.domain == ^domain) case Plausible.Repo.one(query) do - {_, _, rule} -> %PageRule{rule | from_cache?: false} + {_, _, rule = %PageRule{}} -> %PageRule{rule | from_cache?: false} _any -> nil end end diff --git a/lib/plausible/site/cache.ex b/lib/plausible/site/cache.ex index ab085585b8..2c559f66ca 100644 --- a/lib/plausible/site/cache.ex +++ b/lib/plausible/site/cache.ex @@ -63,7 +63,7 @@ defmodule Plausible.Site.Cache do query = from s in base_db_query(), where: s.domain == ^domain case Plausible.Repo.one(query) do - {_, _, site} -> %Site{site | from_cache?: false} + {_, _, site = %Site{}} -> %Site{site | from_cache?: false} _any -> nil end end diff --git a/lib/plausible/stats/query_optimizer.ex b/lib/plausible/stats/query_optimizer.ex index 70e83643b5..c42c7fe653 100644 --- a/lib/plausible/stats/query_optimizer.ex +++ b/lib/plausible/stats/query_optimizer.ex @@ -100,7 +100,7 @@ defmodule Plausible.Stats.QueryOptimizer do end end - defp update_time_in_order_by(query) do + defp update_time_in_order_by(%Query{} = query) do order_by = query.order_by |> Enum.map(fn @@ -126,7 +126,7 @@ defmodule Plausible.Stats.QueryOptimizer do # To avoid showing referrers across hostnames when event:hostname # filter is present for breakdowns, add entry/exit page hostname # filters - defp extend_hostname_filters_to_visit(query) do + defp extend_hostname_filters_to_visit(%Query{} = query) do # Note: Only works since event:hostname is only allowed as a top level filter hostname_filters = query.filters diff --git a/lib/plausible_web/controllers/avatar_controller.ex b/lib/plausible_web/controllers/avatar_controller.ex index 816ed17559..49603f0200 100644 --- a/lib/plausible_web/controllers/avatar_controller.ex +++ b/lib/plausible_web/controllers/avatar_controller.ex @@ -31,7 +31,7 @@ defmodule PlausibleWeb.AvatarController do end @forwarded_headers ["content-type", "cache-control", "expires"] - defp forward_headers(conn, headers) do + defp forward_headers(%Plug.Conn{} = conn, headers) do headers_to_forward = Enum.filter(headers, fn {k, _} -> k in @forwarded_headers end) %Plug.Conn{conn | resp_headers: headers_to_forward} end diff --git a/lib/plausible_web/plugs/favicon.ex b/lib/plausible_web/plugs/favicon.ex index 787ef574cb..89a25fda57 100644 --- a/lib/plausible_web/plugs/favicon.ex +++ b/lib/plausible_web/plugs/favicon.ex @@ -130,7 +130,7 @@ defmodule PlausibleWeb.Favicon do end @forwarded_headers ["content-type", "cache-control", "expires"] - defp forward_headers(conn, headers) do + defp forward_headers(%Plug.Conn{} = conn, headers) do headers_to_forward = Enum.filter(headers, fn {k, _} -> k in @forwarded_headers end) %Plug.Conn{conn | resp_headers: headers_to_forward} end diff --git a/test/plausible/consolidated_view/cache_test_sync.exs b/test/plausible/consolidated_view/cache_sync_test.exs similarity index 81% rename from test/plausible/consolidated_view/cache_test_sync.exs rename to test/plausible/consolidated_view/cache_sync_test.exs index 12f8871516..1007aa0fe2 100644 --- a/test/plausible/consolidated_view/cache_test_sync.exs +++ b/test/plausible/consolidated_view/cache_sync_test.exs @@ -1,4 +1,4 @@ -defmodule Plausible.CondolidatedView.CacheTestSync do +defmodule Plausible.CondolidatedView.CacheSyncTest do use Plausible.DataCase, async: false on_ee do @@ -27,10 +27,10 @@ defmodule Plausible.CondolidatedView.CacheTestSync do } ] = Sentry.Test.pop_sentry_reports() end - end - defp start_test_cache(cache_name) do - %{start: {m, f, a}} = Cache.child_spec(cache_name: cache_name) - apply(m, f, a) + defp start_test_cache(cache_name) do + %{start: {m, f, a}} = Cache.child_spec(cache_name: cache_name) + apply(m, f, a) + end end end diff --git a/test/plausible/segments/segment_test.exs b/test/plausible/segments/segment_test.exs index 355aa635d6..a0318f3cd9 100644 --- a/test/plausible/segments/segment_test.exs +++ b/test/plausible/segments/segment_test.exs @@ -1,9 +1,10 @@ defmodule Plausible.Segments.SegmentTest do use ExUnit.Case - doctest Plausible.Segments.Segment, import: true + alias Plausible.Segments.Segment + doctest Segment, import: true setup do - segment = %Plausible.Segments.Segment{ + segment = %Segment{ name: "any name", type: :personal, segment_data: %{"filters" => ["is", "visit:page", ["/blog"]]}, @@ -15,7 +16,7 @@ defmodule Plausible.Segments.SegmentTest do end test "changeset has required fields" do - assert Plausible.Segments.Segment.changeset(%Plausible.Segments.Segment{}, %{}).errors == [ + assert Segment.changeset(%Segment{}, %{}).errors == [ segment_data: {"property \"filters\" must be an array with at least one member", []}, name: {"can't be blank", [validation: :required]}, segment_data: {"can't be blank", [validation: :required]}, @@ -27,7 +28,7 @@ defmodule Plausible.Segments.SegmentTest do test "changeset does not allow setting owner_id to nil (setting to nil happens with database triggers)", %{segment: valid_segment} do - assert Plausible.Segments.Segment.changeset( + assert Segment.changeset( valid_segment, %{ owner_id: nil @@ -38,7 +39,7 @@ defmodule Plausible.Segments.SegmentTest do end test "changeset forbids too long name", %{segment: valid_segment} do - assert Plausible.Segments.Segment.changeset( + assert Segment.changeset( valid_segment, %{ name: String.duplicate("a", 256) @@ -51,7 +52,7 @@ defmodule Plausible.Segments.SegmentTest do end test "changeset forbids too large segment_data", %{segment: valid_segment} do - assert Plausible.Segments.Segment.changeset( + assert Segment.changeset( valid_segment, %{ segment_data: @@ -65,9 +66,9 @@ defmodule Plausible.Segments.SegmentTest do end test "changeset allows setting nil owner_id to a user id (to be able to recover dangling site segments)", - %{segment: valid_segment} do - assert Plausible.Segments.Segment.changeset( - %Plausible.Segments.Segment{ + %{segment: valid_segment = %Segment{}} do + assert Segment.changeset( + %Segment{ valid_segment | owner_id: nil }, @@ -78,7 +79,7 @@ defmodule Plausible.Segments.SegmentTest do end test "changeset requires segment_data to be structured as expected", %{segment: valid_segment} do - assert Plausible.Segments.Segment.changeset( + assert Segment.changeset( valid_segment, %{ segment_data: %{"filters" => 1, "labels" => true, "other" => []} @@ -93,7 +94,7 @@ defmodule Plausible.Segments.SegmentTest do end test "changeset forbids empty filters list", %{segment: valid_segment} do - assert Plausible.Segments.Segment.changeset( + assert Segment.changeset( valid_segment, %{ segment_data: %{ @@ -107,7 +108,7 @@ defmodule Plausible.Segments.SegmentTest do end test "changeset permits well-structured segment data", %{segment: valid_segment} do - assert Plausible.Segments.Segment.changeset( + assert Segment.changeset( valid_segment, %{ segment_data: %{ diff --git a/test/plausible_web/controllers/api/external_stats_controller/query_special_metrics_test.exs b/test/plausible_web/controllers/api/external_stats_controller/query_special_metrics_test.exs index 9d755becbd..d0e0373da4 100644 --- a/test/plausible_web/controllers/api/external_stats_controller/query_special_metrics_test.exs +++ b/test/plausible_web/controllers/api/external_stats_controller/query_special_metrics_test.exs @@ -224,7 +224,12 @@ defmodule PlausibleWeb.Api.ExternalStatsController.QuerySpecialMetricsTest do build(:pageview, user_id: 1, pathname: "/two", timestamp: ~N[2021-01-01 00:10:00]), build(:pageview, user_id: 3, pathname: "/one", timestamp: ~N[2021-01-01 00:00:00]), build(:pageview, user_id: 3, pathname: "/never-exit", timestamp: ~N[2021-01-01 00:00:00]), - build(:event, user_id: 3, name: "a", pathname: "/one", timestamp: ~N[2021-01-01 00:00:00]), + build(:event, + user_id: 3, + name: "a", + pathname: "/one", + timestamp: ~N[2021-01-01 00:00:00] + ), build(:pageview, user_id: 3, pathname: "/one", timestamp: ~N[2021-01-01 00:10:00]) ]) @@ -323,8 +328,16 @@ defmodule PlausibleWeb.Api.ExternalStatsController.QuerySpecialMetricsTest do test "in visit:exit_page breakdown filtered by visit:country", %{conn: conn, site: site} do populate_stats(site, [ - build(:pageview, pathname: "/one", country_code: "EE", timestamp: ~N[2021-01-01 00:00:00]), - build(:pageview, pathname: "/one", country_code: "US", timestamp: ~N[2021-01-01 00:00:00]), + build(:pageview, + pathname: "/one", + country_code: "EE", + timestamp: ~N[2021-01-01 00:00:00] + ), + build(:pageview, + pathname: "/one", + country_code: "US", + timestamp: ~N[2021-01-01 00:00:00] + ), build(:pageview, user_id: 1, pathname: "/one", diff --git a/test/plausible_web/controllers/api/external_stats_controller/timeseries_test.exs b/test/plausible_web/controllers/api/external_stats_controller/timeseries_test.exs index 297756bf81..8d12db3452 100644 --- a/test/plausible_web/controllers/api/external_stats_controller/timeseries_test.exs +++ b/test/plausible_web/controllers/api/external_stats_controller/timeseries_test.exs @@ -615,7 +615,11 @@ defmodule PlausibleWeb.Api.ExternalStatsController.TimeseriesTest do user_id: @user_id, timestamp: ~N[2021-01-01 00:25:00] ), - build(:pageview, pathname: "/blog", user_id: @user_id, timestamp: ~N[2021-01-01 00:25:00]), + build(:pageview, + pathname: "/blog", + user_id: @user_id, + timestamp: ~N[2021-01-01 00:25:00] + ), build(:pageview, pathname: "/", timestamp: ~N[2021-01-01 00:25:00]) ]) diff --git a/test/plausible_web/controllers/api/stats_controller/conversions_test.exs b/test/plausible_web/controllers/api/stats_controller/conversions_test.exs index 79e261e1c5..2adcf23417 100644 --- a/test/plausible_web/controllers/api/stats_controller/conversions_test.exs +++ b/test/plausible_web/controllers/api/stats_controller/conversions_test.exs @@ -1166,8 +1166,18 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do populate_stats(site, site_import.id, [ build(:imported_pages, page: "/", visitors: 1, pageviews: 1, date: ~D[2021-01-01]), - build(:imported_pages, page: "/blog/one", visitors: 2, pageviews: 2, date: ~D[2021-01-01]), - build(:imported_pages, page: "/blog/two", visitors: 3, pageviews: 3, date: ~D[2021-01-01]), + build(:imported_pages, + page: "/blog/one", + visitors: 2, + pageviews: 2, + date: ~D[2021-01-01] + ), + build(:imported_pages, + page: "/blog/two", + visitors: 3, + pageviews: 3, + date: ~D[2021-01-01] + ), build(:imported_pages, page: "/blog/three", visitors: 4, diff --git a/test/plausible_web/controllers/api/stats_controller/pages_test.exs b/test/plausible_web/controllers/api/stats_controller/pages_test.exs index 3db45b0c69..72f70802b9 100644 --- a/test/plausible_web/controllers/api/stats_controller/pages_test.exs +++ b/test/plausible_web/controllers/api/stats_controller/pages_test.exs @@ -849,7 +849,11 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do site: site } do populate_stats(site, [ - build(:pageview, user_id: @user_id, pathname: "/blog", timestamp: ~N[2020-01-01 00:00:00]), + build(:pageview, + user_id: @user_id, + pathname: "/blog", + timestamp: ~N[2020-01-01 00:00:00] + ), build(:engagement, user_id: @user_id, pathname: "/blog", diff --git a/test/plausible_web/controllers/api/stats_controller/screen_sizes_test.exs b/test/plausible_web/controllers/api/stats_controller/screen_sizes_test.exs index c239735757..18b7139333 100644 --- a/test/plausible_web/controllers/api/stats_controller/screen_sizes_test.exs +++ b/test/plausible_web/controllers/api/stats_controller/screen_sizes_test.exs @@ -21,8 +21,16 @@ defmodule PlausibleWeb.Api.StatsController.ScreenSizesTest do test "returns bounce_rate and visit_duration when detailed=true", %{conn: conn, site: site} do populate_stats(site, [ - build(:pageview, user_id: 123, timestamp: ~N[2021-01-01 12:00:00], screen_size: "Desktop"), - build(:pageview, user_id: 123, timestamp: ~N[2021-01-01 12:10:00], screen_size: "Desktop"), + build(:pageview, + user_id: 123, + timestamp: ~N[2021-01-01 12:00:00], + screen_size: "Desktop" + ), + build(:pageview, + user_id: 123, + timestamp: ~N[2021-01-01 12:10:00], + screen_size: "Desktop" + ), build(:pageview, timestamp: ~N[2021-01-01 12:00:00], screen_size: "Desktop"), build(:pageview, timestamp: ~N[2021-01-01 12:00:00], screen_size: "Laptop") ]) diff --git a/test/plausible_web/controllers/api/stats_controller/top_stats_test.exs b/test/plausible_web/controllers/api/stats_controller/top_stats_test.exs index 2e4d691a6a..04dbfdc1fa 100644 --- a/test/plausible_web/controllers/api/stats_controller/top_stats_test.exs +++ b/test/plausible_web/controllers/api/stats_controller/top_stats_test.exs @@ -1279,7 +1279,11 @@ defmodule PlausibleWeb.Api.StatsController.TopStatsTest do populate_stats(site, [ build(:pageview, pathname: "/index", hostname: "example.com"), build(:pageview, pathname: "/index", hostname: "example.com", user_id: @user_id), - build(:pageview, pathname: "/blog/post1", hostname: "blog.example.com", user_id: @user_id), + build(:pageview, + pathname: "/blog/post1", + hostname: "blog.example.com", + user_id: @user_id + ), build(:pageview, pathname: "/blog/post2", hostname: "blog.example.com") ]) @@ -1306,8 +1310,16 @@ defmodule PlausibleWeb.Api.StatsController.TopStatsTest do populate_stats(site, [ build(:pageview, pathname: "/index", hostname: "example.com"), build(:pageview, pathname: "/index", hostname: "example.com", user_id: @user_id), - build(:pageview, pathname: "/blog/post1", hostname: "blog.example.com", user_id: @user_id), - build(:pageview, pathname: "/blog/post2", hostname: "blog.example.com", user_id: @user_id), + build(:pageview, + pathname: "/blog/post1", + hostname: "blog.example.com", + user_id: @user_id + ), + build(:pageview, + pathname: "/blog/post2", + hostname: "blog.example.com", + user_id: @user_id + ), build(:pageview, pathname: "/blog/post2", hostname: "blog.example.com"), build(:pageview, pathname: "/blog/post2", hostname: "about.example.com") ]) @@ -1336,8 +1348,16 @@ defmodule PlausibleWeb.Api.StatsController.TopStatsTest do populate_stats(site, [ build(:pageview, pathname: "/index", hostname: "example.com"), build(:pageview, pathname: "/index", hostname: "example.com", user_id: @user_id), - build(:pageview, pathname: "/blog/post1", hostname: "blog.example.com", user_id: @user_id), - build(:pageview, pathname: "/blog/post2", hostname: "blog.example.com", user_id: @user_id), + build(:pageview, + pathname: "/blog/post1", + hostname: "blog.example.com", + user_id: @user_id + ), + build(:pageview, + pathname: "/blog/post2", + hostname: "blog.example.com", + user_id: @user_id + ), build(:pageview, pathname: "/blog/post3", hostname: "blog.example.com"), build(:pageview, pathname: "/blog/post2", diff --git a/test/workers/oban_error_reporter_test.exs b/test/workers/oban_error_reporter_test.exs index 7bbac9d7c6..397689d4f8 100644 --- a/test/workers/oban_error_reporter_test.exs +++ b/test/workers/oban_error_reporter_test.exs @@ -37,7 +37,8 @@ defmodule ObanErrorReporterTest do ) end) - assert log =~ "[error] ** (BadMapError) expected a map, got: :bad_job" + assert log =~ "(BadMapError)" + assert log =~ ":bad_job" end end end