From ef7351ccb81bca1ee4db2581fe836c35178ba438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cristian=20=C3=81lvarez=20Belaustegui?= Date: Tue, 17 Dec 2024 18:24:02 +0100 Subject: [PATCH] Update and unify dev scripts (#119) This pull request replaces the previous `live.exs` and `dev.exs` scripts with a new `dev.exs` script based on the Phoenix Playground project that includes both the Controller and LiveView examples. There are multiple benefits of this refactor: - We can work using a single script instead of two - Phoenix Plaground provides autoreload functionality by default - Phoenix Playground simplifies process management and can start a supervision tree - Dependencies are automatically fetched and compiled separately from those in the `mix.exs` file. To start the dev server you just run: ``` iex dev.exs ``` --- README.md | 6 +- dev.exs | 372 +++++++++++++++++++++++++++++++++--------------------- live.exs | 153 ---------------------- 3 files changed, 227 insertions(+), 304 deletions(-) delete mode 100644 live.exs diff --git a/README.md b/README.md index 98b1de1..5e79930 100644 --- a/README.md +++ b/README.md @@ -62,10 +62,8 @@ mix assets.watch ### Development server -We have a `dev.exs` script that starts a development server. - -To run it together with an `IEx` console you can do: +We have a `dev.exs` script based on [Phoenix Playground](https://github.com/phoenix-playground/phoenix_playground) that starts a development server. ``` -iex -S mix dev +iex dev.exs ``` diff --git a/dev.exs b/dev.exs index 895749d..a09f496 100644 --- a/dev.exs +++ b/dev.exs @@ -1,169 +1,217 @@ -####################################### -# Development Server for ErrorTracker. +# This is the development server for Errortracker built on the PhoenixLiveDashboard project. +# To start the development server run: +# $ iex dev.exs # -# Based on PhoenixLiveDashboard code. -# -# Usage: -# -# $ iex -S mix dev -####################################### -Logger.configure(level: :debug) +Mix.install([ + {:ecto_sqlite3, ">= 0.0.0"}, + {:error_tracker, path: "."}, + {:phoenix_playground, "~> 0.1.7"} +]) -# Get configuration -Config.Reader.read!("config/config.exs", env: :dev) +otp_app = :error_tracker_dev -# Prepare the repo -adapter = - case Application.get_env(:error_tracker, :ecto_adapter) do - :postgres -> Ecto.Adapters.Postgres - :mysql -> Ecto.Adapters.MyXQL - :sqlite3 -> Ecto.Adapters.SQLite3 - end +Application.put_all_env( + error_tracker_dev: [ + {ErrorTrackerDev.Repo, [database: "priv/repo/dev.db"]} + ], + error_tracker: [ + {:application, otp_app}, + {:otp_app, otp_app}, + {:repo, ErrorTrackerDev.Repo} + ] +) defmodule ErrorTrackerDev.Repo do - use Ecto.Repo, otp_app: :error_tracker, adapter: adapter + require Logger + use Ecto.Repo, otp_app: otp_app, adapter: Ecto.Adapters.SQLite3 + + defmodule Migration do + use Ecto.Migration + + def up, do: ErrorTracker.Migration.up() + def down, do: ErrorTracker.Migration.down() + end + + def migrate do + Ecto.Migrator.run(__MODULE__, [{0, __MODULE__.Migration}], :up, all: true) + end end -_ = adapter.storage_up(ErrorTrackerDev.Repo.config()) - -# Configures the endpoint -Application.put_env(:error_tracker, ErrorTrackerDevWeb.Endpoint, - url: [host: "localhost"], - secret_key_base: "Hu4qQN3iKzTV4fJxhorPQlA/osH9fAMtbtjVS58PFgfw3ja5Z18Q/WSNR9wP4OfW", - live_view: [signing_salt: "hMegieSe"], - http: [port: System.get_env("PORT") || 4000], - debug_errors: true, - check_origin: false, - pubsub_server: ErrorTrackerDev.PubSub, - watchers: [ - tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]} - ], - live_reload: [ - patterns: [ - ~r"dev.exs$", - ~r"dist/.*(js|css|png|jpeg|jpg|gif|svg)$", - ~r"lib/error_tracker/web/(live|views)/.*(ex)$", - ~r"lib/error_tracker/web/templates/.*(ex)$" - ] - ] -) +defmodule ErrorTrackerDev.Controller do + use Phoenix.Controller, formats: [:html] + use Phoenix.Component -# Setup up the ErrorTracker configuration -Application.put_env(:error_tracker, :repo, ErrorTrackerDev.Repo) -Application.put_env(:error_tracker, :otp_app, :error_tracker_dev) -Application.put_env(:error_tracker, :prefix, "private") + plug :put_layout, false + plug :put_view, __MODULE__ -defmodule ErrorTrackerDevWeb.PageController do - import Plug.Conn + def index(conn, _params) do + render(conn) + end - def init(opts), do: opts + def index(assigns) do + ~H""" +

ErrorTracker Dev server

- def call(conn, :index) do - content(conn, """ -

ErrorTracker Dev Server

-
Open ErrorTracker
-
Generate Plug exception
-
Generate Router 404
-
Raise NoRouteError from a controller
-
Generate Exception
-
Generate Exit
- """) + + """ end - def call(conn, :noroute) do - ErrorTracker.add_breadcrumb("ErrorTrackerDevWeb.PageController.no_route") - raise Phoenix.Router.NoRouteError, conn: conn, router: ErrorTrackerDevWeb.Router + def noroute(conn, _params) do + ErrorTracker.add_breadcrumb("ErrorTrackerDev.Controller.noroute/2") + + raise Phoenix.Router.NoRouteError, conn: conn, router: ErrorTrackerDev.Router end - def call(_conn, :exception) do - ErrorTracker.add_breadcrumb("ErrorTrackerDevWeb.PageController.exception") + def exception(_conn, _params) do + ErrorTracker.add_breadcrumb("ErrorTrackerDev.Controller.exception/2") - raise CustomException, + raise ErrorTrackerDev.Exception, message: "This is a controller exception", bread_crumbs: ["First", "Second"] end - def call(_conn, :exit) do - ErrorTracker.add_breadcrumb("ErrorTrackerDevWeb.PageController.exit") + def exit(_conn, _params) do + ErrorTracker.add_breadcrumb("ErrorTrackerDev.Controller.exit/2") + exit(:timeout) end +end - defp content(conn, content) do - conn - |> put_resp_header("content-type", "text/html") - |> send_resp(200, "#{content}") +defmodule ErrorTrackerDev.Live do + use Phoenix.LiveView + + def mount(params, _session, socket) do + if params["crash_on_mount"] do + raise("Crashed on mount/3") + end + + {:ok, socket} end -end -defmodule CustomException do - defexception [:message, :bread_crumbs] -end + def handle_params(params, _uri, socket) do + if params["crash_on_handle_params"] do + raise "Crashed on handle_params/3" + end -defmodule ErrorTrackerDevWeb.ErrorView do - def render("404.html", _assigns) do - "This is a 404" + {:noreply, socket} end - def render("500.html", _assigns) do - "This is a 500" + def handle_event("crash_on_handle_event", _params, _socket) do + raise "Crashed on handle_event/3" + end + + def handle_event("crash_on_render", _params, socket) do + {:noreply, assign(socket, crash_on_render: true)} + end + + def handle_event("genserver-timeout", _params, socket) do + GenServer.call(ErrorTrackerDev.GenServer, :timeout, 2000) + {:noreply, socket} + end + + def render(assigns) do + if Map.has_key?(assigns, :crash_on_render) do + raise "Crashed on render/1" + end + + ~H""" +

ErrorTracker Dev server

+ + <.link href="/dev/errors" target="_blank">Open the ErrorTracker dashboard + +

+ Errors are stored in the priv/repo/dev.db + database, which is automatically created by this script.
+ If you want to clear the state stop the script, run the following command and start it again.

rm priv/repo/dev.db priv/repo/dev.db-shm priv/repo/dev.db-wal
+

+ +

LiveView examples

+ + + +

Controller examples

+ + + """ end end -defmodule ErrorTrackerDevWeb.Router do +defmodule ErrorTrackerDev.Router do use Phoenix.Router use ErrorTracker.Web, :router + import Phoenix.LiveView.Router + pipeline :browser do - plug :fetch_session - plug :protect_from_forgery + plug :accepts, [:html] + plug :put_root_layout, html: {PhoenixPlayground.Layout, :root} + plug :put_secure_browser_headers end scope "/" do pipe_through :browser - get "/", ErrorTrackerDevWeb.PageController, :index - get "/noroute", ErrorTrackerDevWeb.PageController, :noroute - get "/exception", ErrorTrackerDevWeb.PageController, :exception - get "/exit", ErrorTrackerDevWeb.PageController, :exit + + live "/", ErrorTrackerDev.Live + get "/noroute", ErrorTrackerDev.Controller, :noroute + get "/exception", ErrorTrackerDev.Controller, :exception + get "/exit", ErrorTrackerDev.Controller, :exit scope "/dev" do - error_tracker_dashboard "/errors", csp_nonce_assign_key: :my_csp_nonce + error_tracker_dashboard "/errors", csp_nonce_assign_key: :custom_csp_nonce end end end -defmodule ErrorTrackerDevWeb.Endpoint do - use Phoenix.Endpoint, otp_app: :error_tracker +defmodule ErrorTrackerDev.Endpoint do + use Phoenix.Endpoint, otp_app: :phoenix_playground use ErrorTracker.Integrations.Plug - @session_options [ - store: :cookie, - key: "_error_tracker_dev", - signing_salt: "/VEDsdfsffMnp5", - same_site: "Lax" - ] - - socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] + # Default PhoenixPlayground.Endpoint + plug Plug.Logger + socket "/live", Phoenix.LiveView.Socket + plug Plug.Static, from: {:phoenix, "priv/static"}, at: "/assets/phoenix" + plug Plug.Static, from: {:phoenix_live_view, "priv/static"}, at: "/assets/phoenix_live_view" socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket - plug Phoenix.LiveReloader - plug Phoenix.CodeReloader - - plug Plug.Session, @session_options + plug Phoenix.CodeReloader, reloader: &PhoenixPlayground.CodeReloader.reload/2 - plug Plug.RequestId - plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] - plug :add_breadcrumb - plug :maybe_exception + # Use a custom Content Security Policy plug :set_csp - plug ErrorTrackerDevWeb.Router - - def add_breadcrumb(conn, _) do - ErrorTracker.add_breadcrumb("ErrorTrackerDevWeb.Endpoint.add_breadcrumb") - conn - end - - def maybe_exception(%Plug.Conn{path_info: ["plug-exception"]}, _), do: raise("Plug exception") - def maybe_exception(conn, _), do: conn + # Raise an exception in the /plug_exception path + plug :plug_exception + # Our custom router which allows us to have regular controllers and live views + plug ErrorTrackerDev.Router defp set_csp(conn, _opts) do nonce = 10 |> :crypto.strong_rand_bytes() |> Base.encode64() @@ -174,50 +222,80 @@ defmodule ErrorTrackerDevWeb.Endpoint do ] conn - |> Plug.Conn.assign(:my_csp_nonce, "#{nonce}") + |> Plug.Conn.assign(:custom_csp_nonce, "#{nonce}") |> Plug.Conn.put_resp_header("content-security-policy", Enum.join(policies, " ")) end + + defp plug_exception(conn = %Plug.Conn{path_info: path_info}, _opts) when is_list(path_info) do + if "plug_exception" in path_info, + do: raise("Crashed in Endpoint"), + else: conn + end end -defmodule ErrorTrackerDev.Telemetry do - require Logger +defmodule ErrorTrackerDev.ErrorView do + def render("404.html", _assigns) do + "This is a 404" + end + + def render("500.html", _assigns) do + "This is a 500" + end +end + +defmodule ErrorTrackerDev.GenServer do + use GenServer - def start do - :telemetry.attach_many( - "error-tracker-events", - [ - [:error_tracker, :error, :new], - [:error_tracker, :error, :resolved], - [:error_tracker, :error, :unresolved], - [:error_tracker, :occurrence, :new] - ], - &__MODULE__.handle_event/4, - [] - ) + # Client - Logger.info("Telemtry attached") + def start_link(_) do + GenServer.start_link(__MODULE__, %{}) end - def handle_event(event, measure, metadata, _opts) do - dbg([event, measure, metadata]) + # Server (callbacks) + + @impl true + def init(initial_state) do + {:ok, initial_state} end -end -Application.put_env(:phoenix, :serve_endpoints, true) + @impl true + def handle_call(:timeout, _from, state) do + :timer.sleep(5000) + {:reply, state, state} + end +end -Task.async(fn -> - children = [ - {Phoenix.PubSub, [name: ErrorTrackerDev.PubSub, adapter: Phoenix.PubSub.PG2]}, - ErrorTrackerDev.Repo, - ErrorTrackerDevWeb.Endpoint - ] +defmodule ErrorTrackerDev.Exception do + defexception [:message, :bread_crumbs] +end - ErrorTrackerDev.Telemetry.start() +defmodule ErrorTrackerDev.Telemetry do + def handle_event(event, measure, metadata, _opts) do + dbg([event, measure, metadata]) + end +end - {:ok, _} = Supervisor.start_link(children, strategy: :one_for_one) +PhoenixPlayground.start( + endpoint: ErrorTrackerDev.Endpoint, + child_specs: [ + {ErrorTrackerDev.Repo, []}, + {ErrorTrackerDev.GenServer, [name: ErrorTrackerDev.GenServer]} + ], + open_browser: false, + debug_errors: false +) - # Automatically run the migrations on boot - Ecto.Migrator.run(ErrorTrackerDev.Repo, :up, all: true, log_migrations_sql: :debug) +ErrorTrackerDev.Repo.migrate() - Process.sleep(:infinity) -end) +:telemetry.attach_many( + "error-tracker-events", + [ + [:error_tracker, :error, :new], + [:error_tracker, :error, :resolved], + [:error_tracker, :error, :unresolved], + [:error_tracker, :occurrence, :new] + ], + &ErrorTrackerDev.Telemetry.handle_event/4, + [] +) diff --git a/live.exs b/live.exs deleted file mode 100644 index a5f10bd..0000000 --- a/live.exs +++ /dev/null @@ -1,153 +0,0 @@ -Mix.install([ - {:phoenix_playground, "~> 0.1.7"}, - {:postgrex, "~> 0.19.3"}, - {:error_tracker, path: "."} -]) - -# Set up the repository for the Error Tracker -defmodule ErrorTrackerDev.Repo do - use Ecto.Repo, otp_app: :error_tracker, adapter: Ecto.Adapters.Postgres -end - -Application.put_env(:error_tracker, :repo, ErrorTrackerDev.Repo) -Application.put_env(:error_tracker, :application, :error_tracker_dev) -Application.put_env(:error_tracker, :prefix, "private") -Application.put_env(:error_tracker, :otp_app, :error_tracker_dev) - -Application.put_env(:error_tracker, ErrorTrackerDev.Repo, - url: "ecto://postgres:postgres@127.0.0.1/error_tracker_dev" -) - -# This migration will set up the database structure -defmodule Migration0 do - use Ecto.Migration - - def up, do: ErrorTracker.Migration.up(prefix: "private") - def down, do: ErrorTracker.Migration.down(prefix: "private") -end - -defmodule ErrorTrackerDev.TimeoutGenServer do - use GenServer - - # Client - - def start_link(_) do - GenServer.start_link(__MODULE__, %{}) - end - - # Server (callbacks) - - @impl true - def init(initial_state) do - {:ok, initial_state} - end - - @impl true - def handle_call(:timeout, _from, state) do - :timer.sleep(5000) - {:reply, state, state} - end -end - -defmodule DemoLive do - use Phoenix.LiveView - - def mount(params, _session, socket) do - if params["crash"] == "mount" do - raise "Crashing on mount" - end - - {:ok, assign(socket, count: 0)} - end - - def render(assigns) do - if assigns.count == 5 do - raise "Crash on render" - end - - ~H""" - <%= @count %> - - - - - - <.link href="/?crash=mount">Crash on mount - <.link patch="/?crash=handle_params">Crash on handle_params - - - """ - end - - def handle_event("inc", _params, socket) do - {:noreply, assign(socket, count: socket.assigns.count + 1)} - end - - def handle_event("dec", _params, socket) do - {:noreply, assign(socket, count: socket.assigns.count - 1)} - end - - def handle_event("error", _params, _socket) do - raise "Crash on handle_event" - end - - def handle_event("genserver-timeout", _params, socket) do - GenServer.call(TimeoutGenServer, :timeout, 2000) - {:noreply, socket} - end - - def handle_params(params, _uri, socket) do - if params["crash"] == "handle_params" do - raise "Crash on handle_params" - end - - {:noreply, socket} - end -end - -defmodule DemoRouter do - use Phoenix.Router - use ErrorTracker.Web, :router - - import Phoenix.LiveView.Router - - pipeline :browser do - plug :put_root_layout, html: {PhoenixPlayground.Layout, :root} - end - - scope "/" do - pipe_through :browser - live "/", DemoLive - error_tracker_dashboard "/errors" - end -end - -defmodule DemoEndpoint do - use Phoenix.Endpoint, otp_app: :phoenix_playground - plug Plug.Logger - socket "/live", Phoenix.LiveView.Socket - plug Plug.Static, from: {:phoenix, "priv/static"}, at: "/assets/phoenix" - plug Plug.Static, from: {:phoenix_live_view, "priv/static"}, at: "/assets/phoenix_live_view" - socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket - plug Phoenix.LiveReloader - plug Phoenix.CodeReloader, reloader: &PhoenixPlayground.CodeReloader.reload/2 - plug DemoRouter -end - -PhoenixPlayground.start( - endpoint: DemoEndpoint, - child_specs: [ - {ErrorTrackerDev.Repo, []}, - {ErrorTrackerDev.TimeoutGenServer, [name: TimeoutGenServer]} - ] -) - -# Create the database if it does not exist and run migrations if needed -_ = Ecto.Adapters.Postgres.storage_up(ErrorTrackerDev.Repo.config()) - -Ecto.Migrator.run(ErrorTrackerDev.Repo, [{0, Migration0}], :up, - all: true, - log_migrations_sql: :debug -)