Skip to content

Improve web dashboard navigation #141

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 28, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
36 changes: 35 additions & 1 deletion lib/error_tracker/web/components/core_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ defmodule ErrorTracker.Web.CoreComponents do
"""
end

attr :name, :string, values: ~w[bell bell-slash]
attr :name, :string, values: ~w[bell bell-slash arrow-left arrow-right]

def icon(assigns = %{name: "bell"}) do
~H"""
Expand Down Expand Up @@ -167,4 +167,38 @@ defmodule ErrorTracker.Web.CoreComponents do
</svg>
"""
end

def icon(assigns = %{name: "arrow-left"}) do
~H"""
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="!h-4 !w-4 inline-block"
>
<path
fill-rule="evenodd"
d="M14 8a.75.75 0 0 1-.75.75H4.56l3.22 3.22a.75.75 0 1 1-1.06 1.06l-4.5-4.5a.75.75 0 0 1 0-1.06l4.5-4.5a.75.75 0 0 1 1.06 1.06L4.56 7.25h8.69A.75.75 0 0 1 14 8Z"
clip-rule="evenodd"
/>
</svg>
"""
end

def icon(assigns = %{name: "arrow-right"}) do
~H"""
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="!h-4 !w-4 inline-block"
>
<path
fill-rule="evenodd"
d="M2 8a.75.75 0 0 1 .75-.75h8.69L8.22 4.03a.75.75 0 0 1 1.06-1.06l4.5 4.5a.75.75 0 0 1 0 1.06l-4.5 4.5a.75.75 0 0 1-1.06-1.06l3.22-3.22H2.75A.75.75 0 0 1 2 8Z"
clip-rule="evenodd"
/>
</svg>
"""
end
end
21 changes: 8 additions & 13 deletions lib/error_tracker/web/live/dashboard.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,28 @@ defmodule ErrorTracker.Web.Live.Dashboard do

alias ErrorTracker.Error
alias ErrorTracker.Repo
alias ErrorTracker.Web.Search

@per_page 10

@impl Phoenix.LiveView
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)
|> assign(
path: path,
search: Search.from_params(params),
page: 1,
search_form: Search.to_form(params)
)
|> paginate_errors()}
end

@impl Phoenix.LiveView
def handle_event("search", params, socket) do
{search, _search_form} = search_terms(params["search"] || %{})
search = Search.from_params(params["search"] || %{})

path_w_filters = %URI{socket.assigns.path | query: URI.encode_query(search)}

Expand Down Expand Up @@ -109,15 +113,6 @@ defmodule ErrorTracker.Web.Live.Dashboard do
)
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
Expand Down
2 changes: 1 addition & 1 deletion lib/error_tracker/web/live/dashboard.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
class="border-b bg-gray-400/10 border-y border-gray-900 hover:bg-gray-800/60 last-of-type:border-b-0"
>
<td scope="row" class="px-4 py-4 font-medium text-white relative">
<.link navigate={error_path(@socket, error)} class="absolute inset-1">
<.link navigate={error_path(@socket, error, @search)} class="absolute inset-1">
<span class="sr-only">(<%= sanitize_module(error.kind) %>) <%= error.reason %></span>
</.link>
<p class="whitespace-nowrap text-ellipsis overflow-hidden">
Expand Down
75 changes: 48 additions & 27 deletions lib/error_tracker/web/live/show.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,55 @@ defmodule ErrorTracker.Web.Live.Show do
alias ErrorTracker.Error
alias ErrorTracker.Occurrence
alias ErrorTracker.Repo
alias ErrorTracker.Web.Search

@occurrences_to_navigate 50

@impl Phoenix.LiveView
def mount(%{"id" => id}, _session, socket) do
def mount(params = %{"id" => id}, _session, socket) do
error = Repo.get!(Error, id)
{:ok, assign(socket, error: error, app: Application.fetch_env!(:error_tracker, :otp_app))}

{:ok,
assign(socket,
error: error,
app: Application.fetch_env!(:error_tracker, :otp_app),
search: Search.from_params(params)
)}
end

@impl Phoenix.LiveView
def handle_params(%{"occurrence_id" => occurrence_id}, _uri, socket) do
def handle_params(params, _uri, socket) do
occurrence =
socket.assigns.error
|> Ecto.assoc(:occurrences)
|> Repo.get!(occurrence_id)

socket =
socket
|> assign(:occurrence, occurrence)
|> load_related_occurrences()

{:noreply, socket}
end

def handle_params(_, _uri, socket) do
[occurrence] =
socket.assigns.error
|> Ecto.assoc(:occurrences)
|> order_by([o], desc: o.id)
|> limit(1)
|> Repo.all()
if occurrence_id = params["occurrence_id"] do
socket.assigns.error
|> Ecto.assoc(:occurrences)
|> Repo.get!(occurrence_id)
else
socket.assigns.error
|> Ecto.assoc(:occurrences)
|> order_by([o], desc: o.id)
|> limit(1)
|> Repo.one()
end

socket =
socket
|> assign(:occurrence, occurrence)
|> assign(occurrence: occurrence)
|> load_related_occurrences()

{:noreply, socket}
end

@impl Phoenix.LiveView
def handle_event("occurrence_navigation", %{"occurrence_id" => id}, socket) do
{:noreply,
push_patch(socket,
to: occurrence_path(socket, %Occurrence{error_id: socket.assigns.error.id, id: id})
)}
occurrence_path =
occurrence_path(
socket,
%Occurrence{error_id: socket.assigns.error.id, id: id},
socket.assigns.search
)

{:noreply, push_patch(socket, to: occurrence_path)}
end

@impl Phoenix.LiveView
Expand Down Expand Up @@ -123,9 +126,27 @@ defmodule ErrorTracker.Web.Live.Show do
|> Ecto.assoc(:occurrences)
|> Repo.aggregate(:count)

next_occurrence =
base_query
|> where([o], o.id > ^current_occurrence.id)
|> order_by([o], asc: o.id)
|> limit(1)
|> select([:id, :error_id, :inserted_at])
|> Repo.one()

prev_occurrence =
base_query
|> where([o], o.id < ^current_occurrence.id)
|> order_by([o], desc: o.id)
|> limit(1)
|> select([:id, :error_id, :inserted_at])
|> Repo.one()

socket
|> assign(:occurrences, occurrences)
|> assign(:total_occurrences, total_occurrences)
|> assign(:next, next_occurrence)
|> assign(:prev, prev_occurrence)
end

defp related_occurrences(query, num_results) do
Expand Down
17 changes: 16 additions & 1 deletion lib/error_tracker/web/live/show.html.heex
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<div class="my-6">
<.button type="link" href={dashboard_path(@socket)}>« Back to the dashboard</.button>
<.link navigate={dashboard_path(@socket, @search)}>
<.icon name="arrow-left" /> Back to the dashboard
</.link>
Comment on lines +2 to +4
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Previously we were triggering a full page reload when going back to the dashboard page. With this change we just do a LiveView navigation over the existing websocket connection.

This is faster and does not require negotiating a TLS handshake or reloading all static assets again.

</div>

<div id="header">
Expand Down Expand Up @@ -97,6 +99,19 @@
</option>
</select>
</form>

<nav class="grid grid-cols-2 gap-2 mt-2">
<div class="text-left">
<.link :if={@prev} patch={occurrence_path(@socket, @prev, @search)}>
<.icon name="arrow-left" /> Prev
</.link>
</div>
<div class="text-right">
<.link :if={@next} patch={occurrence_path(@socket, @next, @search)}>
Next <.icon name="arrow-right" />
</.link>
</div>
</nav>
</.section>

<.section title="Error kind">
Expand Down
27 changes: 21 additions & 6 deletions lib/error_tracker/web/router/routes.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,36 @@ defmodule ErrorTracker.Web.Router.Routes do
@doc """
Returns the dashboard path
"""
def dashboard_path(socket = %Socket{}) do
socket.private[:dashboard_path]
def dashboard_path(socket = %Socket{}, params \\ %{}) do
socket
|> dashboard_uri(params)
|> URI.to_string()
end

@doc """
Returns the path to see the details of an error
"""
def error_path(socket = %Socket{}, %Error{id: id}) do
dashboard_path(socket) <> "/#{id}"
def error_path(socket = %Socket{}, %Error{id: id}, params \\ %{}) do
socket
|> dashboard_uri(params)
|> URI.append_path("/#{id}")
Copy link
Contributor

Choose a reason for hiding this comment

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

Much nicer solution ❤️

|> URI.to_string()
end

@doc """
Returns the path to see the details of an occurrence
"""
def occurrence_path(socket = %Socket{}, %Occurrence{id: id, error_id: error_id}) do
dashboard_path(socket) <> "/#{error_id}/#{id}"
def occurrence_path(socket = %Socket{}, %Occurrence{id: id, error_id: error_id}, params \\ %{}) do
socket
|> dashboard_uri(params)
|> URI.append_path("/#{error_id}/#{id}")
|> URI.to_string()
end

defp dashboard_uri(socket = %Socket{}, params) do
%URI{
path: socket.private[:dashboard_path],
query: if(Enum.any?(params), do: URI.encode_query(params))
}
end
end
24 changes: 24 additions & 0 deletions lib/error_tracker/web/search.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule ErrorTracker.Web.Search do
@moduledoc false

@types %{
reason: :string,
source_line: :string,
source_function: :string,
status: :string
}

defp changeset(params) do
Ecto.Changeset.cast({%{}, @types}, params, Map.keys(@types))
end

@spec from_params(map()) :: %{atom() => String.t()}
def from_params(params) do
params |> changeset() |> Ecto.Changeset.apply_changes()
end

@spec to_form(map()) :: Phoenix.HTML.Form.t()
def to_form(params) do
params |> changeset() |> Phoenix.Component.to_form(as: :search)
end
end
17 changes: 17 additions & 0 deletions priv/static/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,10 @@ select {
border-width: 0;
}

.static {
position: static;
}

.absolute {
position: absolute;
}
Expand Down Expand Up @@ -841,6 +845,10 @@ select {
margin-top: 1.5rem;
}

.mt-2 {
margin-top: 0.5rem;
}

.block {
display: block;
}
Expand Down Expand Up @@ -873,6 +881,11 @@ select {
display: none;
}

.size-4 {
width: 1rem;
height: 1rem;
}

.\!h-4 {
height: 1rem !important;
}
Expand Down Expand Up @@ -925,6 +938,10 @@ select {
grid-template-columns: repeat(2, minmax(0, 1fr));
}

.grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}

.flex-col {
flex-direction: column;
}
Expand Down