From 56016ba377efcc75c6db8dab6a073bab108848de Mon Sep 17 00:00:00 2001
From: crbelaus
Date: Mon, 15 Jul 2024 18:58:45 +0200
Subject: [PATCH 01/34] Track errors last occurrence
---
lib/error_tracker.ex | 2 +-
lib/error_tracker/migrations/postgres/v01.ex | 1 +
lib/error_tracker/schemas/error.ex | 4 +++-
3 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/lib/error_tracker.ex b/lib/error_tracker.ex
index 7f45232..c70121b 100644
--- a/lib/error_tracker.ex
+++ b/lib/error_tracker.ex
@@ -16,7 +16,7 @@ defmodule ErrorTracker do
error =
repo().insert!(error,
- on_conflict: [set: [status: :unresolved]],
+ on_conflict: [set: [status: :unresolved, last_occurrence_at: DateTime.utc_now()]],
conflict_target: :fingerprint,
prefix: prefix()
)
diff --git a/lib/error_tracker/migrations/postgres/v01.ex b/lib/error_tracker/migrations/postgres/v01.ex
index 46854d1..056bf1f 100644
--- a/lib/error_tracker/migrations/postgres/v01.ex
+++ b/lib/error_tracker/migrations/postgres/v01.ex
@@ -13,6 +13,7 @@ defmodule ErrorTracker.Migrations.Postgres.V01 do
add :source_function, :text, null: false
add :status, :string, null: false
add :fingerprint, :string, null: false
+ add :last_occurrence_at, :utc_datetime_usec, null: false
timestamps(type: :utc_datetime_usec)
end
diff --git a/lib/error_tracker/schemas/error.ex b/lib/error_tracker/schemas/error.ex
index 828ee53..226a672 100644
--- a/lib/error_tracker/schemas/error.ex
+++ b/lib/error_tracker/schemas/error.ex
@@ -15,6 +15,7 @@ defmodule ErrorTracker.Error do
field :source_function, :string
field :status, Ecto.Enum, values: [:resolved, :unresolved], default: :unresolved
field :fingerprint, :binary
+ field :last_occurrence_at, :utc_datetime_usec
has_many :occurrences, ErrorTracker.Occurrence
@@ -40,7 +41,8 @@ defmodule ErrorTracker.Error do
kind: to_string(kind),
reason: reason,
source_line: "#{source.file}:#{source.line}",
- source_function: "#{source.module}.#{source.function}/#{source.arity}"
+ source_function: "#{source.module}.#{source.function}/#{source.arity}",
+ last_occurrence_at: DateTime.utc_now()
]
fingerprint = :crypto.hash(:sha256, params |> Keyword.values() |> Enum.join())
From 8eb7ee26122e3fee6f8f3e1daf755bd12bc14df3 Mon Sep 17 00:00:00 2001
From: crbelaus
Date: Mon, 15 Jul 2024 18:49:15 +0200
Subject: [PATCH 02/34] WIP: error dashboard index
---
lib/error_tracker.ex | 16 +++-
lib/error_tracker/web/live/dashboard.ex | 74 ++++++++++++++++++-
.../web/live/dashboard.html.heex | 58 +++++++++++++--
3 files changed, 136 insertions(+), 12 deletions(-)
diff --git a/lib/error_tracker.ex b/lib/error_tracker.ex
index c70121b..ad20062 100644
--- a/lib/error_tracker.ex
+++ b/lib/error_tracker.ex
@@ -8,9 +8,11 @@ defmodule ErrorTracker do
"""
@type context :: %{String.t() => any()}
+ alias ErrorTracker.Error
+
def report(exception, stacktrace, given_context \\ %{}) do
{:ok, stacktrace} = ErrorTracker.Stacktrace.new(stacktrace)
- {:ok, error} = ErrorTracker.Error.new(exception, stacktrace)
+ {:ok, error} = Error.new(exception, stacktrace)
context = Map.merge(get_context(), given_context)
@@ -26,6 +28,18 @@ defmodule ErrorTracker do
|> repo().insert!(prefix: prefix())
end
+ def resolve(error = %Error{status: :unresolved}) do
+ changeset = Ecto.Changeset.change(error, status: :resolved)
+
+ repo().update(changeset, prefix: prefix())
+ end
+
+ def unresolve(error = %Error{status: :resolved}) do
+ changeset = Ecto.Changeset.change(error, status: :unresolved)
+
+ repo().update(changeset, prefix: prefix())
+ end
+
def repo do
Application.fetch_env!(:error_tracker, :repo)
end
diff --git a/lib/error_tracker/web/live/dashboard.ex b/lib/error_tracker/web/live/dashboard.ex
index caf12e6..6818f87 100644
--- a/lib/error_tracker/web/live/dashboard.ex
+++ b/lib/error_tracker/web/live/dashboard.ex
@@ -3,9 +3,16 @@ defmodule ErrorTracker.Web.Live.Dashboard do
use ErrorTracker.Web, :live_view
+ import Ecto.Query
+
+ alias ErrorTracker.Error
+
@impl Phoenix.LiveView
def mount(_params, _session, socket) do
- {:ok, assign(socket, :counter, 0)}
+ {:ok,
+ socket
+ |> assign(page: 1, per_page: 5)
+ |> paginate_errors(1)}
end
@impl Phoenix.LiveView
@@ -14,7 +21,68 @@ defmodule ErrorTracker.Web.Live.Dashboard do
end
@impl Phoenix.LiveView
- def handle_event("increment", _params, socket) do
- {:noreply, assign(socket, :counter, socket.assigns.counter + 1)}
+ def handle_event("next-page", _params, socket) do
+ {:noreply, paginate_errors(socket, socket.assigns.page + 1)}
+ end
+
+ @impl Phoenix.LiveView
+ def handle_event("prev-page", %{"_overran" => true}, socket) do
+ {:noreply, paginate_errors(socket, 1)}
+ end
+
+ @impl Phoenix.LiveView
+ def handle_event("prev-page", _params, socket) do
+ if socket.assigns.page > 1 do
+ {:noreply, paginate_errors(socket, socket.assigns.page - 1)}
+ else
+ {:noreply, socket}
+ end
+ end
+
+ @impl Phoenix.LiveView
+ def handle_event("resolve", %{"error_id" => id}, socket) do
+ error = ErrorTracker.repo().get(Error, id, prefix: ErrorTracker.prefix())
+ {:ok, resolved} = ErrorTracker.resolve(error)
+
+ {:noreply, stream_insert(socket, :errors, resolved)}
+ end
+
+ @impl Phoenix.LiveView
+ def handle_event("unresolve", %{"error_id" => id}, socket) do
+ error = ErrorTracker.repo().get(Error, id, prefix: ErrorTracker.prefix())
+ {:ok, unresolved} = ErrorTracker.unresolve(error)
+
+ {:noreply, stream_insert(socket, :errors, unresolved)}
+ end
+
+ defp paginate_errors(socket, new_page) when new_page >= 1 do
+ %{per_page: per_page, page: cur_page} = socket.assigns
+
+ errors =
+ ErrorTracker.repo().all(
+ from(Error,
+ order_by: [desc: :last_occurrence_at],
+ offset: (^new_page - 1) * ^per_page,
+ limit: ^per_page
+ ),
+ prefix: ErrorTracker.prefix()
+ )
+
+ {errors, at, limit} =
+ if new_page >= cur_page do
+ {errors, -1, per_page * 3 * -1}
+ else
+ {Enum.reverse(errors), 0, per_page * 3}
+ end
+
+ case errors do
+ [] ->
+ assign(socket, end_of_errors?: at == -1)
+
+ [_ | _] ->
+ socket
+ |> assign(end_of_errors?: false, page: new_page)
+ |> stream(:errors, errors, at: at, limit: limit)
+ end
end
end
diff --git a/lib/error_tracker/web/live/dashboard.html.heex b/lib/error_tracker/web/live/dashboard.html.heex
index 07f5708..eabc7f3 100644
--- a/lib/error_tracker/web/live/dashboard.html.heex
+++ b/lib/error_tracker/web/live/dashboard.html.heex
@@ -1,8 +1,50 @@
-Hello world!
-Number of presses: <%= @counter %>
-
+
+
+
+ | Error |
+ Source |
+ Last |
+ Status |
+ |
+
+
+ 1, do: "prev-page"}
+ phx-viewport-bottom={if !@end_of_errors?, do: "next-page"}
+ phx-page-loading
+ >
+
+ | <%= String.slice(error.reason, 0..100) %> |
+
+ <%= error.source_line %>
+
+ <%= error.source_function %>
+ |
+
+ <%= error.last_occurrence_at %>
+ |
+
+ <%= error.status %>
+ |
+
+
+
+
+ |
+
+
+
From ae89054d1e76bab26c46684e3644140f3a2f89db Mon Sep 17 00:00:00 2001
From: crbelaus
Date: Wed, 17 Jul 2024 19:24:50 +0200
Subject: [PATCH 03/34] Filters and pagination
---
lib/error_tracker/web/live/dashboard.ex | 117 ++++++++++--------
.../web/live/dashboard.html.heex | 51 ++++++--
mix.exs | 1 +
mix.lock | 1 +
4 files changed, 113 insertions(+), 57 deletions(-)
diff --git a/lib/error_tracker/web/live/dashboard.ex b/lib/error_tracker/web/live/dashboard.ex
index 6818f87..86239d3 100644
--- a/lib/error_tracker/web/live/dashboard.ex
+++ b/lib/error_tracker/web/live/dashboard.ex
@@ -7,82 +7,101 @@ defmodule ErrorTracker.Web.Live.Dashboard do
alias ErrorTracker.Error
+ @per_page 10
+
@impl Phoenix.LiveView
- def mount(_params, _session, socket) do
- {:ok,
- socket
- |> assign(page: 1, per_page: 5)
- |> paginate_errors(1)}
+ def mount(params, _session, socket) do
+ {_search, search_form} = search_terms(params)
+
+ {:ok, assign(socket, page: 1, total_pages: 1, search_form: search_form, errors: [])}
end
@impl Phoenix.LiveView
- def handle_params(_params, _uri, socket) do
- {:noreply, socket}
+ def handle_params(params, uri, socket) do
+ {search, search_form} = search_terms(params)
+
+ path = struct(URI, uri |> URI.parse() |> Map.take([:path, :query]))
+
+ {:noreply,
+ socket
+ |> assign(path: path, search: search, page: 1, search_form: search_form)
+ |> paginate_errors()}
end
@impl Phoenix.LiveView
- def handle_event("next-page", _params, socket) do
- {:noreply, paginate_errors(socket, socket.assigns.page + 1)}
+ def handle_event("search", params, socket) do
+ {search, _search_form} = search_terms(params["search"] || %{})
+
+ path_w_filters = %URI{socket.assigns.path | query: URI.encode_query(search)}
+
+ {:noreply, push_patch(socket, to: URI.to_string(path_w_filters))}
end
@impl Phoenix.LiveView
- def handle_event("prev-page", %{"_overran" => true}, socket) do
- {:noreply, paginate_errors(socket, 1)}
+ def handle_event("next-page", _params, socket) do
+ {:noreply, socket |> assign(page: socket.assigns.page + 1) |> paginate_errors()}
end
@impl Phoenix.LiveView
def handle_event("prev-page", _params, socket) do
- if socket.assigns.page > 1 do
- {:noreply, paginate_errors(socket, socket.assigns.page - 1)}
- else
- {:noreply, socket}
- end
+ {:noreply, socket |> assign(page: socket.assigns.page - 1) |> paginate_errors()}
end
@impl Phoenix.LiveView
def handle_event("resolve", %{"error_id" => id}, socket) do
error = ErrorTracker.repo().get(Error, id, prefix: ErrorTracker.prefix())
- {:ok, resolved} = ErrorTracker.resolve(error)
+ {:ok, _resolved} = ErrorTracker.resolve(error)
- {:noreply, stream_insert(socket, :errors, resolved)}
+ {:noreply, paginate_errors(socket)}
end
@impl Phoenix.LiveView
def handle_event("unresolve", %{"error_id" => id}, socket) do
error = ErrorTracker.repo().get(Error, id, prefix: ErrorTracker.prefix())
- {:ok, unresolved} = ErrorTracker.unresolve(error)
+ {:ok, _unresolved} = ErrorTracker.unresolve(error)
+
+ {:noreply, paginate_errors(socket)}
+ end
+
+ defp paginate_errors(socket) do
+ %{page: page, search: search} = socket.assigns
+ repo = ErrorTracker.repo()
+ prefix = ErrorTracker.prefix()
+
+ query = filter(Error, search)
+
+ total_errors = repo.aggregate(query, :count, prefix: prefix)
+
+ errors_query =
+ query
+ |> order_by(desc: :last_occurrence_at)
+ |> offset((^page - 1) * @per_page)
+ |> limit(@per_page)
+
+ assign(socket,
+ errors: repo.all(errors_query, prefix: prefix),
+ total_pages: (total_errors / @per_page) |> Float.ceil() |> trunc
+ )
+ end
+
+ defp search_terms(params) do
+ data = %{}
+ types = %{reason: :string, source_line: :string, source_function: :string, status: :string}
+
+ changeset = Ecto.Changeset.cast({data, types}, params, Map.keys(types))
+
+ {Ecto.Changeset.apply_changes(changeset), to_form(changeset, as: :search)}
+ end
+
+ defp filter(query, search) do
+ Enum.reduce(search, query, &do_filter/2)
+ end
- {:noreply, stream_insert(socket, :errors, unresolved)}
+ defp do_filter({:status, status}, query) do
+ where(query, [error], error.status == ^status)
end
- defp paginate_errors(socket, new_page) when new_page >= 1 do
- %{per_page: per_page, page: cur_page} = socket.assigns
-
- errors =
- ErrorTracker.repo().all(
- from(Error,
- order_by: [desc: :last_occurrence_at],
- offset: (^new_page - 1) * ^per_page,
- limit: ^per_page
- ),
- prefix: ErrorTracker.prefix()
- )
-
- {errors, at, limit} =
- if new_page >= cur_page do
- {errors, -1, per_page * 3 * -1}
- else
- {Enum.reverse(errors), 0, per_page * 3}
- end
-
- case errors do
- [] ->
- assign(socket, end_of_errors?: at == -1)
-
- [_ | _] ->
- socket
- |> assign(end_of_errors?: false, page: new_page)
- |> stream(:errors, errors, at: at, limit: limit)
- end
+ defp do_filter({field, value}, query) do
+ where(query, [error], ilike(field(error, ^field), ^"%#{value}%"))
end
end
diff --git a/lib/error_tracker/web/live/dashboard.html.heex b/lib/error_tracker/web/live/dashboard.html.heex
index eabc7f3..41dfb69 100644
--- a/lib/error_tracker/web/live/dashboard.html.heex
+++ b/lib/error_tracker/web/live/dashboard.html.heex
@@ -1,3 +1,36 @@
+<.form for={@search_form} id="search" phx-change="search">
+
+
+
+
+
+
@@ -8,14 +41,8 @@
|
- 1, do: "prev-page"}
- phx-viewport-bottom={if !@end_of_errors?, do: "next-page"}
- phx-page-loading
- >
-
+
+
| <%= String.slice(error.reason, 0..100) %> |
<%= error.source_line %>
@@ -48,3 +75,11 @@
|
+
+
+
+
diff --git a/mix.exs b/mix.exs
index 38a3fe7..f40688d 100644
--- a/mix.exs
+++ b/mix.exs
@@ -47,6 +47,7 @@ defmodule ErrorTracker.MixProject do
{:ecto, "~> 3.11"},
{:jason, "~> 1.1"},
{:phoenix_live_view, "~> 0.19 or ~> 1.0"},
+ {:phoenix_ecto, "~> 4.6"},
{:plug, "~> 1.10"},
{:postgrex, ">= 0.0.0"},
# Dev dependencies
diff --git a/mix.lock b/mix.lock
index 0f48b67..61237f0 100644
--- a/mix.lock
+++ b/mix.lock
@@ -20,6 +20,7 @@
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"},
+ "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.2", "3b83b24ab5a2eb071a20372f740d7118767c272db386831b2e77638c4dcc606d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "3f94d025f59de86be00f5f8c5dd7b5965a3298458d21ab1c328488be3b5fcd59"},
"phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.20.14", "70fa101aa0539e81bed4238777498f6215e9dda3461bdaa067cad6908110c364", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "82f6d006c5264f979ed5eb75593d808bbe39020f20df2e78426f4f2d570e2402"},
From b01a326ae9c0a32f4ff61377f8c8d8dab982bbc8 Mon Sep 17 00:00:00 2001
From: crbelaus
Date: Thu, 18 Jul 2024 17:10:32 +0200
Subject: [PATCH 04/34] Remove unused callback
---
lib/error_tracker/web/live/dashboard.ex | 7 -------
1 file changed, 7 deletions(-)
diff --git a/lib/error_tracker/web/live/dashboard.ex b/lib/error_tracker/web/live/dashboard.ex
index 86239d3..8cfc4b0 100644
--- a/lib/error_tracker/web/live/dashboard.ex
+++ b/lib/error_tracker/web/live/dashboard.ex
@@ -9,13 +9,6 @@ defmodule ErrorTracker.Web.Live.Dashboard do
@per_page 10
- @impl Phoenix.LiveView
- def mount(params, _session, socket) do
- {_search, search_form} = search_terms(params)
-
- {:ok, assign(socket, page: 1, total_pages: 1, search_form: search_form, errors: [])}
- end
-
@impl Phoenix.LiveView
def handle_params(params, uri, socket) do
{search, search_form} = search_terms(params)
From adaecd299e7454472af6a9ba817cd3baa5e04ede Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=93scar=20de=20Arriba?=
Date: Thu, 18 Jul 2024 17:27:32 +0200
Subject: [PATCH 05/34] Layout changes
---
lib/error_tracker/web/components/layouts.ex | 2 +
.../web/components/layouts/live.html.heex | 3 +-
.../web/components/layouts/navbar.ex | 63 +++++++++++++++++++
.../web/components/layouts/root.html.heex | 2 +-
4 files changed, 68 insertions(+), 2 deletions(-)
create mode 100644 lib/error_tracker/web/components/layouts/navbar.ex
diff --git a/lib/error_tracker/web/components/layouts.ex b/lib/error_tracker/web/components/layouts.ex
index 33ecade..292429f 100644
--- a/lib/error_tracker/web/components/layouts.ex
+++ b/lib/error_tracker/web/components/layouts.ex
@@ -1,6 +1,8 @@
defmodule ErrorTracker.Web.Layouts do
use ErrorTracker.Web, :html
+ alias ErrorTracker.Web.Layouts.Navbar
+
@css_path :code.priv_dir(:error_tracker) |> Path.join("static/app.css")
@js_path :code.priv_dir(:error_tracker) |> Path.join("static/app.js")
diff --git a/lib/error_tracker/web/components/layouts/live.html.heex b/lib/error_tracker/web/components/layouts/live.html.heex
index c753bc6..959d8b7 100644
--- a/lib/error_tracker/web/components/layouts/live.html.heex
+++ b/lib/error_tracker/web/components/layouts/live.html.heex
@@ -1,3 +1,4 @@
-
+<.live_component module={Navbar} id="navbar" />
+
<%= @inner_content %>
diff --git a/lib/error_tracker/web/components/layouts/navbar.ex b/lib/error_tracker/web/components/layouts/navbar.ex
new file mode 100644
index 0000000..64abaa9
--- /dev/null
+++ b/lib/error_tracker/web/components/layouts/navbar.ex
@@ -0,0 +1,63 @@
+defmodule ErrorTracker.Web.Layouts.Navbar do
+ use ErrorTracker.Web, :live_component
+
+ def render(assigns) do
+ ~H"""
+
+ """
+ end
+
+ attr :to, :string, required: true
+ attr :rest, :global
+
+ slot :inner_block, required: true
+
+ def navbar_item(assigns) do
+ ~H"""
+
+
+ <%= render_slot(@inner_block) %>
+
+
+ """
+ end
+end
diff --git a/lib/error_tracker/web/components/layouts/root.html.heex b/lib/error_tracker/web/components/layouts/root.html.heex
index a802f2e..439fc12 100644
--- a/lib/error_tracker/web/components/layouts/root.html.heex
+++ b/lib/error_tracker/web/components/layouts/root.html.heex
@@ -18,7 +18,7 @@
-
+
<%= @inner_content %>
From 76cd0a0041a76a8533cde988a148c9553c355e0f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=93scar=20de=20Arriba?=
Date: Thu, 18 Jul 2024 18:11:27 +0200
Subject: [PATCH 06/34] WIP dashboard and components
---
lib/error_tracker/web.ex | 2 +
.../web/components/core_components.ex | 70 +++++++++++++
.../web/live/dashboard.html.heex | 97 ++++++++++---------
3 files changed, 124 insertions(+), 45 deletions(-)
create mode 100644 lib/error_tracker/web/components/core_components.ex
diff --git a/lib/error_tracker/web.ex b/lib/error_tracker/web.ex
index f866fb5..46725a3 100644
--- a/lib/error_tracker/web.ex
+++ b/lib/error_tracker/web.ex
@@ -73,6 +73,8 @@ defmodule ErrorTracker.Web do
import Phoenix.HTML
import Phoenix.LiveView.Helpers
+ import ErrorTracker.Web.CoreComponents
+
alias Phoenix.LiveView.JS
end
end
diff --git a/lib/error_tracker/web/components/core_components.ex b/lib/error_tracker/web/components/core_components.ex
new file mode 100644
index 0000000..413b9d6
--- /dev/null
+++ b/lib/error_tracker/web/components/core_components.ex
@@ -0,0 +1,70 @@
+defmodule ErrorTracker.Web.CoreComponents do
+ use Phoenix.Component
+
+ @doc """
+ Renders a button.
+
+ ## Examples
+
+ <.button>Send!
+ <.button phx-click="go" class="ml-2">Send!
+ """
+ attr :type, :string, default: nil
+ attr :class, :string, default: nil
+ attr :rest, :global, include: ~w(disabled form name value)
+
+ slot :inner_block, required: true
+
+ def button(assigns) do
+ ~H"""
+
+ """
+ end
+
+ @doc """
+ Renders a badge.
+
+ ## Examples
+
+ <.badge>Info
+ <.badge color={:red}>Error
+ """
+ attr :color, :atom, default: :blue
+ attr :rest, :global
+
+ slot :inner_block, required: true
+
+ def badge(assigns) do
+ color_class =
+ case assigns.color do
+ :blue -> "bg-blue-900 text-blue-300"
+ :gray -> "bg-gray-700 text-gray-300"
+ :red -> "bg-red-900 text-red-300"
+ :green -> "bg-green-900 text-green-300"
+ :yellow -> "bg-yellow-900 text-yellow-300"
+ :indigo -> "bg-indigo-900 text-indigo-300"
+ :purple -> "bg-purple-900 text-purple-300"
+ :pink -> "bg-pink-900 text-pink-300"
+ :gray -> "bg-gray-700 text-gray-300"
+ :gray -> "bg-gray-700 text-gray-300"
+ end
+
+ assigns = Map.put(assigns, :color_class, color_class)
+
+ ~H"""
+
+ <%= render_slot(@inner_block) %>
+
+ """
+ end
+end
diff --git a/lib/error_tracker/web/live/dashboard.html.heex b/lib/error_tracker/web/live/dashboard.html.heex
index 41dfb69..46bd8b3 100644
--- a/lib/error_tracker/web/live/dashboard.html.heex
+++ b/lib/error_tracker/web/live/dashboard.html.heex
@@ -1,4 +1,4 @@
-<.form for={@search_form} id="search" phx-change="search">
+<.form for={@search_form} id="search" class="mb-4 text-black" phx-change="search">
-
- <%= error.last_occurrence_at %>
+ <%= Calendar.strftime(error.last_occurrence_at, "%c") %>
|
<.badge :if={error.status == :resolved} color={:green}>Resolved
diff --git a/lib/error_tracker/web/live/show.ex b/lib/error_tracker/web/live/show.ex
index 2774b41..7b78e0e 100644
--- a/lib/error_tracker/web/live/show.ex
+++ b/lib/error_tracker/web/live/show.ex
@@ -14,16 +14,16 @@ defmodule ErrorTracker.Web.Live.Show do
{:ok, assign(socket, error: error)}
end
- def handle_params(%{"occurence_id" => occurrence_id}, _uri, socket) do
+ def handle_params(%{"occurrence_id" => occurrence_id}, _uri, socket) do
base_query = Ecto.assoc(socket.assigns.error, :occurrences)
occurrence = Repo.get!(base_query, occurrence_id)
previous_occurrences =
base_query
|> where([o], o.id < ^occurrence.id)
- |> related_occurrences(@occurreneces_to_navigate / 2)
+ |> related_occurrences(round(@occurreneces_to_navigate / 2))
- limit_next_occurrences = @occurreneces_to_navigate - length(previous_occurrences) - 1
+ limit_next_occurrences = dbg(@occurreneces_to_navigate - length(previous_occurrences) - 1)
next_occurrences =
base_query
@@ -32,7 +32,7 @@ defmodule ErrorTracker.Web.Live.Show do
socket =
socket
- |> assign(:occurrences, previous_occurrences ++ occurrence ++ next_occurrences)
+ |> assign(:occurrences, previous_occurrences ++ [occurrence] ++ next_occurrences)
|> assign(:occurrence, occurrence)
{:noreply, socket}
diff --git a/lib/error_tracker/web/live/show.html.heex b/lib/error_tracker/web/live/show.html.heex
index f1f0f45..f08fe1b 100644
--- a/lib/error_tracker/web/live/show.html.heex
+++ b/lib/error_tracker/web/live/show.html.heex
@@ -1,26 +1,45 @@
-<.link navigate="/errors">Back to the dashboard
+
+ <.link navigate="/errors">Back to the dashboard
+
-<%= @error.reason |> String.split("\n", trim: true) |> List.first() %>
+
+
+ <%= @error.reason |> String.split("\n", trim: true) |> List.first() %>
- Full message
-
- <%= @error.reason %>
-
+
+ Full message
+
+ <%= @error.reason %>
+
+
- Source
+
+ Source
-
- <%= @error.source_line %>
-
-
- <%= @error.source_function %>
-
+
+ <%= @error.source_line %>
+ <%= @error.source_function %>
+
+
- Last occurrence
-<%= @error.last_occurrence_at %>
+ Last occurrence
+ <%= Calendar.strftime(@error.last_occurrence_at, "%c") %>
- Details of occurrence
-<%= inspect(@occurrence) %>
+ Details of occurrence
+ <%= inspect(@occurrence) %>
+
- List of occurrences
-<%= inspect(@occurrences) %>
+
+ List of occurrences
+
+
+ <.link
+ :for={occurrence <- @occurrences}
+ class={if occurrence.id == @occurrence.id, do: "font-bold"}
+ patch={"/errors/#{@error.id}?occurrence_id=#{occurrence.id}"}
+ >
+ <%= Calendar.strftime(occurrence.inserted_at, "%c") %>
+
+
+
+
From 3afac95d4f145b6c9c817c0368889fae1cc677b4 Mon Sep 17 00:00:00 2001
From: crbelaus
Date: Sun, 21 Jul 2024 11:41:19 +0200
Subject: [PATCH 18/34] Show occurrence stacktrace and context
---
lib/error_tracker/web/live/show.html.heex | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/lib/error_tracker/web/live/show.html.heex b/lib/error_tracker/web/live/show.html.heex
index f08fe1b..c72332e 100644
--- a/lib/error_tracker/web/live/show.html.heex
+++ b/lib/error_tracker/web/live/show.html.heex
@@ -25,8 +25,17 @@
Last occurrence
<%= Calendar.strftime(@error.last_occurrence_at, "%c") %>
- Details of occurrence
- <%= inspect(@occurrence) %>
+ Stacktrace
+
+
+ <%= to_string(@occurrence.stacktrace) %>
+
+
+ Context
+
+
+ <%= Jason.encode!(@occurrence.context, pretty: true) %>
+
From 552c65529bf75948c347e05b84a8b31874795381 Mon Sep 17 00:00:00 2001
From: crbelaus
Date: Sun, 21 Jul 2024 11:56:48 +0200
Subject: [PATCH 19/34] Show only app frames in stacktrace
---
lib/error_tracker/web/live/show.ex | 2 +-
lib/error_tracker/web/live/show.html.heex | 26 ++++++++++++++++++++---
2 files changed, 24 insertions(+), 4 deletions(-)
diff --git a/lib/error_tracker/web/live/show.ex b/lib/error_tracker/web/live/show.ex
index 7b78e0e..5c246cf 100644
--- a/lib/error_tracker/web/live/show.ex
+++ b/lib/error_tracker/web/live/show.ex
@@ -11,7 +11,7 @@ defmodule ErrorTracker.Web.Live.Show do
def mount(%{"id" => id}, _session, socket) do
error = Repo.get!(Error, id)
- {:ok, assign(socket, error: error)}
+ {:ok, assign(socket, error: error, app: Application.fetch_env!(:error_tracker, :application))}
end
def handle_params(%{"occurrence_id" => occurrence_id}, _uri, socket) do
diff --git a/lib/error_tracker/web/live/show.html.heex b/lib/error_tracker/web/live/show.html.heex
index c72332e..be612df 100644
--- a/lib/error_tracker/web/live/show.html.heex
+++ b/lib/error_tracker/web/live/show.html.heex
@@ -27,9 +27,29 @@
Stacktrace
-
- <%= to_string(@occurrence.stacktrace) %>
-
+
+
+
+
+
+
+ | App |
+ Function |
+ Source |
+
+
+
+
+ | <%= line.application || @app %> |
+ <%= "#{line.module}.#{line.function}/#{line.arity}" %> |
+ <%= "#{line.file}.#{line.line}" %> |
+
+
+
Context
From f9425dc0368481caf383ce20503203c056407703 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=93scar=20de=20Arriba?=
Date: Sun, 21 Jul 2024 12:08:01 +0200
Subject: [PATCH 20/34] Add button as links
---
.../web/components/core_components.ex | 17 ++++++++++++++++-
lib/error_tracker/web/live/show.html.heex | 2 +-
2 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/lib/error_tracker/web/components/core_components.ex b/lib/error_tracker/web/components/core_components.ex
index 084d72a..7ca85a1 100644
--- a/lib/error_tracker/web/components/core_components.ex
+++ b/lib/error_tracker/web/components/core_components.ex
@@ -12,10 +12,25 @@ defmodule ErrorTracker.Web.CoreComponents do
"""
attr :type, :string, default: nil
attr :class, :string, default: nil
- attr :rest, :global, include: ~w(disabled form name value)
+ attr :rest, :global, include: ~w(disabled form name value navigate)
slot :inner_block, required: true
+ def button(%{type: "link"} = assigns) do
+ ~H"""
+
+ <%= render_slot(@inner_block) %>
+
+ """
+ end
+
def button(assigns) do
~H"""
- <.link navigate="/errors">Back to the dashboard
+ <.button type="link" href="/errors">Back to the dashboard
From 645faf665aef73c5a1534daa49d891278dc05b35 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=93scar=20de=20Arriba?=
Date: Sun, 21 Jul 2024 12:35:41 +0200
Subject: [PATCH 21/34] Set configured dashboard path on the assigns
---
lib/error_tracker/web/hooks/set_assigns.ex | 10 ++++++++++
lib/error_tracker/web/router.ex | 11 ++++++++---
2 files changed, 18 insertions(+), 3 deletions(-)
create mode 100644 lib/error_tracker/web/hooks/set_assigns.ex
diff --git a/lib/error_tracker/web/hooks/set_assigns.ex b/lib/error_tracker/web/hooks/set_assigns.ex
new file mode 100644
index 0000000..653aab0
--- /dev/null
+++ b/lib/error_tracker/web/hooks/set_assigns.ex
@@ -0,0 +1,10 @@
+defmodule ErrorTracker.Web.Hooks.SetAssigns do
+ @moduledoc """
+ Mounting hooks to set environment configuration on the socket.
+ """
+ import Phoenix.Component
+
+ def on_mount({:set_dashboard_path, path}, _params, _session, socket) do
+ {:cont, assign(socket, :dashboard_path, path)}
+ end
+end
diff --git a/lib/error_tracker/web/router.ex b/lib/error_tracker/web/router.ex
index 31952d0..8254b34 100644
--- a/lib/error_tracker/web/router.ex
+++ b/lib/error_tracker/web/router.ex
@@ -7,7 +7,7 @@ defmodule ErrorTracker.Web.Router do
It requires a path in which you are going to serve the web interface.
"""
defmacro error_tracker_dashboard(path, opts \\ []) do
- {session_name, session_opts} = parse_options(opts)
+ {session_name, session_opts} = parse_options(opts, path)
quote do
scope unquote(path), alias: false, as: false do
@@ -16,14 +16,19 @@ defmodule ErrorTracker.Web.Router do
live_session unquote(session_name), unquote(session_opts) do
live "/", ErrorTracker.Web.Live.Dashboard, :index, as: unquote(session_name)
live "/:id", ErrorTracker.Web.Live.Show, :show, as: unquote(session_name)
+ live "/:id/:occurrence_id", ErrorTracker.Web.Live.Show, :show, as: unquote(session_name)
end
end
end
end
@doc false
- def parse_options(opts) do
- on_mount = Keyword.get(opts, :on_mount, [])
+ def parse_options(opts, path) do
+ custom_on_mount = Keyword.get(opts, :on_mount, [])
+
+ on_mount =
+ [{ErrorTracker.Web.Hooks.SetAssigns, {:set_dashboard_path, path}}] ++ custom_on_mount
+
session_name = Keyword.get(opts, :as, :error_tracker_dashboard)
session_opts = [
From 14cbbde2668390c52e131539b2e399924c2b3911 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=93scar=20de=20Arriba?=
Date: Sun, 21 Jul 2024 12:35:51 +0200
Subject: [PATCH 22/34] Fix button as link
---
lib/error_tracker/web/components/core_components.ex | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lib/error_tracker/web/components/core_components.ex b/lib/error_tracker/web/components/core_components.ex
index 7ca85a1..7aba7a4 100644
--- a/lib/error_tracker/web/components/core_components.ex
+++ b/lib/error_tracker/web/components/core_components.ex
@@ -12,13 +12,13 @@ defmodule ErrorTracker.Web.CoreComponents do
"""
attr :type, :string, default: nil
attr :class, :string, default: nil
- attr :rest, :global, include: ~w(disabled form name value navigate)
+ attr :rest, :global, include: ~w(disabled form name value href patch navigate)
slot :inner_block, required: true
def button(%{type: "link"} = assigns) do
~H"""
-
<%= render_slot(@inner_block) %>
-
+
"""
end
From 2c027edb4a2c83f4f0e0af1abf94b5f53eb49e8f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=93scar=20de=20Arriba?=
Date: Sun, 21 Jul 2024 12:36:09 +0200
Subject: [PATCH 23/34] Implement routes generator helper
---
lib/error_tracker/web.ex | 1 +
.../web/live/dashboard.html.heex | 2 +-
lib/error_tracker/web/live/show.ex | 2 +-
lib/error_tracker/web/live/show.html.heex | 4 ++--
lib/error_tracker/web/router/routes.ex | 24 +++++++++++++++++++
5 files changed, 29 insertions(+), 4 deletions(-)
create mode 100644 lib/error_tracker/web/router/routes.ex
diff --git a/lib/error_tracker/web.ex b/lib/error_tracker/web.ex
index 46725a3..f7640bf 100644
--- a/lib/error_tracker/web.ex
+++ b/lib/error_tracker/web.ex
@@ -74,6 +74,7 @@ defmodule ErrorTracker.Web do
import Phoenix.LiveView.Helpers
import ErrorTracker.Web.CoreComponents
+ import ErrorTracker.Web.Router.Routes
alias Phoenix.LiveView.JS
end
diff --git a/lib/error_tracker/web/live/dashboard.html.heex b/lib/error_tracker/web/live/dashboard.html.heex
index cf1fe59..948c5b6 100644
--- a/lib/error_tracker/web/live/dashboard.html.heex
+++ b/lib/error_tracker/web/live/dashboard.html.heex
@@ -52,7 +52,7 @@
scope="row"
class="px-4 py-4 font-medium text-white whitespace-nowrap text-ellipsis overflow-hidden"
>
- <.link navigate={"/errors/#{error.id}"}>
+ <.link navigate={error_path(assigns, error)}>
(<%= error.kind %>) <%= error.reason %>
diff --git a/lib/error_tracker/web/live/show.ex b/lib/error_tracker/web/live/show.ex
index 5c246cf..172426b 100644
--- a/lib/error_tracker/web/live/show.ex
+++ b/lib/error_tracker/web/live/show.ex
@@ -55,7 +55,7 @@ defmodule ErrorTracker.Web.Live.Show do
defp related_occurrences(query, num_results \\ @occurreneces_to_navigate) do
query
|> order_by([o], desc: o.id)
- |> select([:id, :inserted_at])
+ |> select([:id, :error_id, :inserted_at])
|> limit(^num_results)
|> Repo.all()
end
diff --git a/lib/error_tracker/web/live/show.html.heex b/lib/error_tracker/web/live/show.html.heex
index a29cec8..977edd7 100644
--- a/lib/error_tracker/web/live/show.html.heex
+++ b/lib/error_tracker/web/live/show.html.heex
@@ -1,5 +1,5 @@
- <.button type="link" href="/errors">Back to the dashboard
+ <.button type="link" href={dashboard_path(assigns)}>Back to the dashboard
@@ -65,7 +65,7 @@
<.link
:for={occurrence <- @occurrences}
class={if occurrence.id == @occurrence.id, do: "font-bold"}
- patch={"/errors/#{@error.id}?occurrence_id=#{occurrence.id}"}
+ patch={occurrence_path(assigns, occurrence)}
>
<%= Calendar.strftime(occurrence.inserted_at, "%c") %>
diff --git a/lib/error_tracker/web/router/routes.ex b/lib/error_tracker/web/router/routes.ex
new file mode 100644
index 0000000..5945b42
--- /dev/null
+++ b/lib/error_tracker/web/router/routes.ex
@@ -0,0 +1,24 @@
+defmodule ErrorTracker.Web.Router.Routes do
+ @moduledoc """
+ Module used to generate dashboard routes.
+ """
+
+ alias ErrorTracker.Error
+ alias ErrorTracker.Occurrence
+
+ @doc """
+ Returns the dashboard path
+ """
+ def dashboard_path(%{dashboard_path: dashboard_path}), do: dashboard_path
+
+ @doc """
+ Returns the path to see the details of an error
+ """
+ def error_path(assigns, %Error{id: id}), do: dashboard_path(assigns) <> "/#{id}"
+
+ @doc """
+ Returns the path to see the details of an occurrence
+ """
+ def occurrence_path(assigns, %Occurrence{id: id, error_id: error_id}),
+ do: dashboard_path(assigns) <> "/#{error_id}/#{id}"
+end
From e9c42023592083e330f346301c84d07d07dc9f3f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=93scar=20de=20Arriba?=
Date: Sun, 21 Jul 2024 12:38:46 +0200
Subject: [PATCH 24/34] Happy credo
---
lib/error_tracker/web/components/core_components.ex | 2 +-
lib/error_tracker/web/live/show.ex | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/error_tracker/web/components/core_components.ex b/lib/error_tracker/web/components/core_components.ex
index 7aba7a4..4c50fbc 100644
--- a/lib/error_tracker/web/components/core_components.ex
+++ b/lib/error_tracker/web/components/core_components.ex
@@ -16,7 +16,7 @@ defmodule ErrorTracker.Web.CoreComponents do
slot :inner_block, required: true
- def button(%{type: "link"} = assigns) do
+ def button(assigns = %{type: "link"}) do
~H"""
<.link
class={[
diff --git a/lib/error_tracker/web/live/show.ex b/lib/error_tracker/web/live/show.ex
index 172426b..bb13ace 100644
--- a/lib/error_tracker/web/live/show.ex
+++ b/lib/error_tracker/web/live/show.ex
@@ -23,7 +23,7 @@ defmodule ErrorTracker.Web.Live.Show do
|> where([o], o.id < ^occurrence.id)
|> related_occurrences(round(@occurreneces_to_navigate / 2))
- limit_next_occurrences = dbg(@occurreneces_to_navigate - length(previous_occurrences) - 1)
+ limit_next_occurrences = @occurreneces_to_navigate - length(previous_occurrences) - 1
next_occurrences =
base_query
From 1bab26bf2875ad64fd515b65dc52ae6b87b2459f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=93scar=20de=20Arriba?=
Date: Sun, 21 Jul 2024 12:59:04 +0200
Subject: [PATCH 25/34] Refactor how related occurrences are loaded
---
lib/error_tracker/web/live/show.ex | 74 +++++++++++++++++++++---------
1 file changed, 52 insertions(+), 22 deletions(-)
diff --git a/lib/error_tracker/web/live/show.ex b/lib/error_tracker/web/live/show.ex
index bb13ace..53ebaae 100644
--- a/lib/error_tracker/web/live/show.ex
+++ b/lib/error_tracker/web/live/show.ex
@@ -7,7 +7,7 @@ defmodule ErrorTracker.Web.Live.Show do
alias ErrorTracker.Error
alias ErrorTracker.Repo
- @occurreneces_to_navigate 50
+ @occurrences_to_navigate 50
def mount(%{"id" => id}, _session, socket) do
error = Repo.get!(Error, id)
@@ -15,44 +15,74 @@ defmodule ErrorTracker.Web.Live.Show do
end
def handle_params(%{"occurrence_id" => occurrence_id}, _uri, socket) do
- base_query = Ecto.assoc(socket.assigns.error, :occurrences)
- occurrence = Repo.get!(base_query, occurrence_id)
-
- previous_occurrences =
- base_query
- |> where([o], o.id < ^occurrence.id)
- |> related_occurrences(round(@occurreneces_to_navigate / 2))
-
- limit_next_occurrences = @occurreneces_to_navigate - length(previous_occurrences) - 1
-
- next_occurrences =
- base_query
- |> where([o], o.id > ^occurrence.id)
- |> related_occurrences(limit_next_occurrences)
+ occurrence =
+ socket.assigns.error
+ |> Ecto.assoc(:occurrences)
+ |> Repo.get!(occurrence_id)
socket =
socket
- |> assign(:occurrences, previous_occurrences ++ [occurrence] ++ next_occurrences)
|> assign(:occurrence, occurrence)
+ |> load_related_occurrences()
{:noreply, socket}
end
def handle_params(_, _uri, socket) do
- base_query = Ecto.assoc(socket.assigns.error, :occurrences)
-
- occurrences = related_occurrences(base_query)
- occurrence = Repo.get!(base_query, hd(occurrences).id)
+ [occurrence] =
+ socket.assigns.error
+ |> Ecto.assoc(:occurrences)
+ |> order_by([o], desc: o.id)
+ |> limit(1)
+ |> Repo.all()
socket =
socket
- |> assign(:occurrences, occurrences)
|> assign(:occurrence, occurrence)
+ |> load_related_occurrences()
{:noreply, socket}
end
- defp related_occurrences(query, num_results \\ @occurreneces_to_navigate) do
+ defp load_related_occurrences(socket) do
+ current_occurrence = socket.assigns.occurrence
+ base_query = Ecto.assoc(socket.assigns.error, :occurrences)
+
+ half_limit = floor(@occurrences_to_navigate / 2)
+
+ previous_occurrences_query = where(base_query, [o], o.id < ^current_occurrence.id)
+ next_occurrences_query = where(base_query, [o], o.id > ^current_occurrence.id)
+ previous_count = Repo.aggregate(previous_occurrences_query, :count)
+ next_count = Repo.aggregate(next_occurrences_query, :count)
+
+ {previous_limit, next_limit} =
+ cond do
+ previous_count < half_limit and next_count < half_limit ->
+ {previous_count, next_count}
+
+ previous_count < half_limit ->
+ {previous_count, @occurrences_to_navigate - previous_count - 1}
+
+ next_count < half_limit ->
+ {@occurrences_to_navigate - next_count - 1, next_count}
+
+ true ->
+ {half_limit, half_limit}
+ end
+
+ occurrences =
+ [
+ related_occurrences(next_occurrences_query, next_limit),
+ current_occurrence,
+ related_occurrences(previous_occurrences_query, previous_limit)
+ ]
+ |> List.flatten()
+ |> Enum.reverse()
+
+ assign(socket, :occurrences, occurrences)
+ end
+
+ defp related_occurrences(query, num_results) do
query
|> order_by([o], desc: o.id)
|> select([:id, :error_id, :inserted_at])
From d9aa8186bd3a5e38583bf1a6d7f5b825e50c0edc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=93scar=20de=20Arriba?=
Date: Sun, 21 Jul 2024 13:56:41 +0200
Subject: [PATCH 26/34] WIP UI
---
lib/error_tracker/web/live/dashboard.html.heex | 2 +-
lib/error_tracker/web/live/show.html.heex | 17 ++++++++++++-----
2 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/lib/error_tracker/web/live/dashboard.html.heex b/lib/error_tracker/web/live/dashboard.html.heex
index 948c5b6..570987c 100644
--- a/lib/error_tracker/web/live/dashboard.html.heex
+++ b/lib/error_tracker/web/live/dashboard.html.heex
@@ -50,7 +50,7 @@
<.link navigate={error_path(assigns, error)}>
(<%= error.kind %>) <%= error.reason %>
diff --git a/lib/error_tracker/web/live/show.html.heex b/lib/error_tracker/web/live/show.html.heex
index 977edd7..b206181 100644
--- a/lib/error_tracker/web/live/show.html.heex
+++ b/lib/error_tracker/web/live/show.html.heex
@@ -1,11 +1,18 @@
-
+
<.button type="link" href={dashboard_path(assigns)}>Back to the dashboard
-
-
- <%= @error.reason |> String.split("\n", trim: true) |> List.first() %>
+
+
+
Full message
@@ -58,7 +65,7 @@
-
+
List of occurrences
From e06e6888ec242436dd57582798cc0b919b43f401 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=93scar=20de=20Arriba?=
Date: Sun, 21 Jul 2024 13:59:04 +0200
Subject: [PATCH 27/34] Update navbar and layout
---
lib/error_tracker/web/components/layouts/live.html.heex | 4 ++--
lib/error_tracker/web/components/layouts/navbar.ex | 9 ++++++---
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/lib/error_tracker/web/components/layouts/live.html.heex b/lib/error_tracker/web/components/layouts/live.html.heex
index 959d8b7..864819d 100644
--- a/lib/error_tracker/web/components/layouts/live.html.heex
+++ b/lib/error_tracker/web/components/layouts/live.html.heex
@@ -1,4 +1,4 @@
-<.live_component module={Navbar} id="navbar" />
-
+<.live_component module={Navbar} id="navbar" {assigns} />
+
<%= @inner_content %>
diff --git a/lib/error_tracker/web/components/layouts/navbar.ex b/lib/error_tracker/web/components/layouts/navbar.ex
index 7063e1d..d4478d5 100644
--- a/lib/error_tracker/web/components/layouts/navbar.ex
+++ b/lib/error_tracker/web/components/layouts/navbar.ex
@@ -5,10 +5,13 @@ defmodule ErrorTracker.Web.Layouts.Navbar do
def render(assigns) do
~H"""
|
- <%= Calendar.strftime(error.last_occurrence_at, "%c") %>
+ <%= Calendar.strftime(error.last_occurrence_at, "%c %Z") %>
|
<.badge :if={error.status == :resolved} color={:green}>Resolved
diff --git a/lib/error_tracker/web/live/show.html.heex b/lib/error_tracker/web/live/show.html.heex
index ac040d1..be84ef7 100644
--- a/lib/error_tracker/web/live/show.html.heex
+++ b/lib/error_tracker/web/live/show.html.heex
@@ -4,7 +4,7 @@
-
-
- Full message
-
- <%= @error.reason %>
-
-
-
-
- Source
-
-
- <%= @error.source_line %>
- <%= @error.source_function %>
-
-
-
- Last occurrence
- <%= Calendar.strftime(@error.last_occurrence_at, "%c") %>
-
- Stacktrace
-
-
-
-
-
-
-
- | App |
- Function |
- Source |
-
-
-
-
- | <%= line.application || @app %> |
- <%= "#{line.module}.#{line.function}/#{line.arity}" %> |
- <%= "#{line.file}.#{line.line}" %> |
-
-
-
-
- Context
-
-
- <%= Jason.encode!(@occurrence.context, pretty: true) %>
-
+
+ <.section title="Full message">
+ <%= @error.reason %>
+
+
+ <.section title="Source">
+
+ <%= @error.source_line %>
+ <%= @error.source_function %>
+
+
+ <.section title="Stacktrace">
+
+
+
+
+
+
+
+
+
+ (<%= line.application || @app %>) |
+
+ <%= "#{line.module}.#{line.function}/#{line.arity}" %>
+ <%= "#{line.file}.#{line.line}" %>
+ |
+
+
+
+
+
+
+ <.section title="Context">
+ <%= Jason.encode!(@occurrence.context, pretty: true) %>
+
-
- Occurrence
+ <.section title="Occurrence">
-
+
-
- Error kind
+ <.section title="Error kind">
<%= @error.kind %>
-
+
-
- Last seen
+ <.section title="Last seen">
<%= Calendar.strftime(@error.last_occurrence_at, "%c %Z") %>
-
+
-
- Status
+ <.section title="Status" title_class="mb-3">
<.badge :if={@error.status == :resolved} color={:green}>Resolved
<.badge :if={@error.status == :unresolved} color={:red}>Unresolved
-
+
-
+ <.section>
<.button :if={@error.status == :unresolved} phx-click="resolve">
Mark as resolved
@@ -105,6 +89,6 @@
<.button :if={@error.status == :resolved} phx-click="unresolve">
Mark as unresolved
-
+
From d713724044b6c68554b3350e4f66a681abc618fd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=93scar=20de=20Arriba?=
Date: Sun, 21 Jul 2024 21:03:21 +0200
Subject: [PATCH 32/34] Sanitize module names and format datetimes
---
lib/error_tracker/web.ex | 1 +
lib/error_tracker/web/helpers.ex | 10 ++++++++
.../web/live/dashboard.html.heex | 6 ++---
lib/error_tracker/web/live/show.html.heex | 24 ++++++++++---------
4 files changed, 27 insertions(+), 14 deletions(-)
create mode 100644 lib/error_tracker/web/helpers.ex
diff --git a/lib/error_tracker/web.ex b/lib/error_tracker/web.ex
index f7640bf..eb291c6 100644
--- a/lib/error_tracker/web.ex
+++ b/lib/error_tracker/web.ex
@@ -74,6 +74,7 @@ defmodule ErrorTracker.Web do
import Phoenix.LiveView.Helpers
import ErrorTracker.Web.CoreComponents
+ import ErrorTracker.Web.Helpers
import ErrorTracker.Web.Router.Routes
alias Phoenix.LiveView.JS
diff --git a/lib/error_tracker/web/helpers.ex b/lib/error_tracker/web/helpers.ex
new file mode 100644
index 0000000..c1bb71e
--- /dev/null
+++ b/lib/error_tracker/web/helpers.ex
@@ -0,0 +1,10 @@
+defmodule ErrorTracker.Web.Helpers do
+ @moduledoc false
+
+ @doc false
+ def sanitize_module(<<"Elixir.", str::binary>>), do: str
+ def sanitize_module(str), do: str
+
+ @doc false
+ def format_datetime(%DateTime{} = dt), do: Calendar.strftime(dt, "%c %Z")
+end
diff --git a/lib/error_tracker/web/live/dashboard.html.heex b/lib/error_tracker/web/live/dashboard.html.heex
index 0b6e8f8..35d24f6 100644
--- a/lib/error_tracker/web/live/dashboard.html.heex
+++ b/lib/error_tracker/web/live/dashboard.html.heex
@@ -53,16 +53,16 @@
class="px-4 py-4 font-medium text-white whitespace-nowrap text-ellipsis overflow-hidden"
>
<.link navigate={error_path(assigns, error)}>
- (<%= error.kind %>) <%= error.reason %>
+ (<%= sanitize_module(error.kind) %>) <%= error.reason %>
- <%= error.source_function %>
+ <%= sanitize_module(error.source_function) %>
<%= error.source_line %>
- <%= Calendar.strftime(error.last_occurrence_at, "%c %Z") %>
+ <%= format_datetime(error.last_occurrence_at) %>
|
<.badge :if={error.status == :resolved} color={:green}>Resolved
diff --git a/lib/error_tracker/web/live/show.html.heex b/lib/error_tracker/web/live/show.html.heex
index c55bdcd..0025b06 100644
--- a/lib/error_tracker/web/live/show.html.heex
+++ b/lib/error_tracker/web/live/show.html.heex
@@ -4,10 +4,12 @@
@@ -18,9 +20,9 @@
<.section title="Source">
-
- <%= @error.source_line %>
- <%= @error.source_function %>
+
+ <%= sanitize_module(@error.source_function) %>
+ <%= @error.source_line %>
<.section title="Stacktrace">
@@ -34,12 +36,12 @@
-
+
(<%= line.application || @app %>) |
- <%= "#{line.module}.#{line.function}/#{line.arity}" %>
+ <%= "#{sanitize_module(line.module)}.#{line.function}/#{line.arity}" %>
<%= "#{line.file}.#{line.line}" %>
|
@@ -49,7 +51,7 @@
<.section title="Context">
- <%= Jason.encode!(@occurrence.context, pretty: true) %>
+ <%= Jason.encode!(@occurrence.context, pretty: true) %>
@@ -62,18 +64,18 @@
value={occurrence.id}
selected={occurrence.id == @occurrence.id}
>
- <%= Calendar.strftime(occurrence.inserted_at, "%c %Z") %>
+ <%= format_datetime(occurrence.inserted_at) %>
<.section title="Error kind">
- <%= @error.kind %>
+ <%= sanitize_module(@error.kind) %>
<.section title="Last seen">
- <%= Calendar.strftime(@error.last_occurrence_at, "%c %Z") %>
+ <%= format_datetime(@error.last_occurrence_at) %>
<.section title="Status" title_class="mb-3">
From e56c5d6b251641ff81e1e7532c15a8c10b692b18 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=93scar=20de=20Arriba?=
Date: Sun, 21 Jul 2024 21:10:56 +0200
Subject: [PATCH 33/34] Better checkbox placement
---
lib/error_tracker/web/live/show.html.heex | 19 ++++++++++++-------
1 file changed, 12 insertions(+), 7 deletions(-)
diff --git a/lib/error_tracker/web/live/show.html.heex b/lib/error_tracker/web/live/show.html.heex
index 0025b06..c01f7c4 100644
--- a/lib/error_tracker/web/live/show.html.heex
+++ b/lib/error_tracker/web/live/show.html.heex
@@ -27,13 +27,18 @@
<.section title="Stacktrace">
-
-
-
+
+
From ce424dc7f61d87d39e284a4574551119949433ab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=93scar=20de=20Arriba?=
Date: Sun, 21 Jul 2024 21:14:34 +0200
Subject: [PATCH 34/34] Happy credo
---
lib/error_tracker/web/helpers.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/error_tracker/web/helpers.ex b/lib/error_tracker/web/helpers.ex
index c1bb71e..2309a7e 100644
--- a/lib/error_tracker/web/helpers.ex
+++ b/lib/error_tracker/web/helpers.ex
@@ -6,5 +6,5 @@ defmodule ErrorTracker.Web.Helpers do
def sanitize_module(str), do: str
@doc false
- def format_datetime(%DateTime{} = dt), do: Calendar.strftime(dt, "%c %Z")
+ def format_datetime(dt = %DateTime{}), do: Calendar.strftime(dt, "%c %Z")
end
| | |