Packages
ex_debug_toolbar
0.5.0
A debug web toolbar for Phoenix projects to display all sorts of information about request
Current section
Files
Jump to
Current section
Files
web/views/toolbar_view.ex
defmodule ExDebugToolbar.ToolbarView do @moduledoc false use ExDebugToolbar.Web, :view alias ExDebugToolbar.Data.Timeline alias ExDebugToolbar.{Breakpoint, Request} alias Plug.Conn @millisecond System.convert_time_unit(1, :millisecond, :native) @default_conn %{ assigns: %{ layout: {"none", "none"} }, private: %{ phoenix_controller: "none", phoenix_endpoint: "none", phoenix_action: "none", phoenix_view: "none", phoenix_template: "none" } } def log_color_class(%{level: level}) do case level do :debug -> "text-muted" :error -> "danger" :info -> "info" :warn -> "warning" _ -> "" end end def conn_status_color_class(%{status: status}) do cond do status <= 199 -> "info" status <= 299 -> "success" status <= 399 -> "info" true -> "danger" end end def history_row_color(conn) do case conn_status_color_class(conn) do "success" -> nil color -> color end end def log_timestamp_to_string({date, {h, m, s, _ms}}) do {date, {h, m, s}} |> NaiveDateTime.from_erl! |> to_string end def ecto_color_class(time) do cond do time > 50 * @millisecond -> "danger" time > 20 * @millisecond -> "warning" time > 10 * @millisecond -> "info" true -> "" end end def controller_action(%Conn{} = conn) do conn = conn_with_defaults(conn) "#{get_controller(conn)} :: #{conn.private.phoenix_action}" end def conn_details(%Conn{} = conn) do conn = conn_with_defaults(conn) {layout_view, layout_template} = case conn.assigns.layout do false -> @default_conn.assigns.layout layout -> layout end [ "Endpoint": conn.private.phoenix_endpoint, "Controller": get_controller(conn), "Action": conn.private.phoenix_action, "Template": conn.private.phoenix_template, "View": conn.private.phoenix_view, "Layout View": layout_view, "Layout Template": layout_template, ] end def rendered_templates(%Timeline{} = timeline) do timeline |> Timeline.get_all_events |> Stream.filter(&String.starts_with?(&1.name, "template#")) |> Enum.reduce(%{}, fn event, acc -> Map.update( acc, event.name, %{count: 1, durations: [event.duration], min: 0, max: 0, avg: 0, total: 0}, &(%{&1 | count: &1.count + 1, durations: [event.duration | &1.durations]}) ) end) |> Stream.map(fn {name, stats} -> {name, %{stats | min: Enum.min(stats.durations), max: Enum.max(stats.durations), total: Enum.sum(stats.durations), avg: div(Enum.sum(stats.durations), Enum.count(stats.durations)) }} end) |> Stream.map(fn {name, stats} -> {String.trim_leading(name, "template#"), stats} end) |> Enum.sort_by(fn {_, stats} -> -stats.total end) end def stats_popover_text(stats) do ~w(min max total)a |> Stream.map(&Map.get(stats, &1)) |> Stream.map(&native_time_to_string/1) |> (&Enum.zip(~w(Fastest Slowest Total), &1)).() |> Enum.map(fn {label, value} -> label <> ": " <> value end) |> Enum.join("<br>") end def controller_times(%Timeline{} = timeline) do event_names = MapSet.new ~w(controller.call controller.render) events = timeline |> Timeline.get_all_events |> Enum.filter(&MapSet.member?(event_names, &1.name)) {call, render} = case events do [call, render] -> {call, render} [call] -> {call, %Timeline.Event{}} end [ "Controller": call.duration - render.duration, "Templates": render.duration, ] end def ecto_inline_queries(queries) do queries |> Enum.filter(fn {_, _, type} -> type == :inline end) end def ecto_parallel_queries(queries) do queries |> Enum.filter(fn {_, _, type} -> type == :parallel end) end def breakpoint_code_snippet_start_line(%Breakpoint{code_snippet: code_snippet}) do code_snippet |> hd |> Tuple.to_list |> List.last end def breakpoint_sorted_binding(%Breakpoint{binding: binding}) do binding |> Keyword.keys |> Enum.sort end def breakpoint_relative_line(%Breakpoint{code_snippet: code_snippet, line: line}) do code_snippet |> Enum.find_index(fn {_, n} -> n == line end) |> Kernel.+(1) end def collapse_history(requests) do {group, acc} = requests |> Enum.reduce({[], []}, fn request, {[],[]} -> {[request], []} request, {[prev_request | _] = group, acc} -> if similar_request?(request, prev_request) do {[request | group], acc} else {[request], [group | acc]} end end) [group | acc] |> Enum.reverse |> Enum.map(&Enum.reverse/1) end def breakpoint_uuid(%Request{uuid: request_id}, %Breakpoint{id: id}) do %Breakpoint.UUID{request_id: request_id, breakpoint_id: id} end defp similar_request?(%{conn: conn}, %{conn: prev_conn}) do conn.status == prev_conn.status and conn.method == prev_conn.method and conn.request_path == prev_conn.request_path end def history_row_collapse_class(0), do: "last-request" def history_row_collapse_class(_), do: "prev-request" defp get_controller(%Conn{private: private}) do private.phoenix_controller |> to_string |> String.trim_leading("Elixir.") end defp conn_with_defaults(%Conn{} = conn) do ~w(assigns private)a |> Enum.reduce(conn, fn key, conn -> defaults = Map.get(@default_conn, key) Map.update! conn, key, &Map.merge(defaults, &1 || %{}) end) endend