Skip to content
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
4 changes: 2 additions & 2 deletions lib/live_debugger/common_types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ defmodule LiveDebugger.CommonTypes do
Type for state of a channel that hosts a LiveView.
"""
@type channel_state() :: %{
socket: %Phoenix.LiveView.Socket{},
components: {map(), any(), any()}
socket: Phoenix.LiveView.Socket.t(),
components: list(map())
}

@type cid() :: %Phoenix.LiveComponent.CID{cid: integer()}
Expand Down
22 changes: 19 additions & 3 deletions lib/live_debugger/gen_servers/state_server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ defmodule LiveDebugger.GenServers.StateServer do

use GenServer

alias LiveDebugger.Services.System.ProcessService
alias LiveDebugger.Utils.PubSub, as: PubSubUtils
alias LiveDebugger.CommonTypes
alias LiveDebugger.Structs.Trace
alias LiveDebugger.Services.LiveViewDebugService

@ets_table_name :lvdbg_states

Expand Down Expand Up @@ -77,8 +77,15 @@ defmodule LiveDebugger.GenServers.StateServer do
def handle_info({:process_status, _}, state), do: {:noreply, state}

defp save_state(%Trace{pid: pid} = trace) do
with {:ok, channel_state} <- ProcessService.state(pid) do
with {:ok, socket} <- LiveViewDebugService.socket(pid),
{:ok, components} <- LiveViewDebugService.live_components(pid) do
record_id = record_id(pid)

channel_state = %{
socket: socket,
components: components
}

:ets.insert(@ets_table_name, {record_id, channel_state})

publish_state_changed(trace, channel_state)
Expand Down Expand Up @@ -114,7 +121,16 @@ defmodule LiveDebugger.GenServers.StateServer do
{:ok, channel_state}

[] ->
ProcessService.state(pid)
with {:ok, socket} <- LiveDebugger.Services.LiveViewDebugService.socket(pid),
{:ok, components} <-
LiveDebugger.Services.LiveViewDebugService.live_components(pid) do
channel_state = %{
socket: socket,
components: components
}

{:ok, channel_state}
end
end
end
end
Expand Down
23 changes: 8 additions & 15 deletions lib/live_debugger/services/channel_service.ex
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ defmodule LiveDebugger.Services.ChannelService do
"""
@spec node_ids(channel_state :: CommonTypes.channel_state()) ::
{:ok, [TreeNode.id()]} | {:error, term()}
def node_ids(channel_state) do
component_cids = channel_state |> get_state_components() |> Map.keys()
pid = channel_state.socket.root_pid
def node_ids(%{socket: socket, components: components}) do
component_cids = components |> Enum.map(& &1.cid)
pid = socket.root_pid

{:ok, Enum.map(component_cids, fn cid -> %Phoenix.LiveComponent.CID{cid: cid} end) ++ [pid]}
end
Expand All @@ -75,30 +75,23 @@ defmodule LiveDebugger.Services.ChannelService do
end)
end

defp children_cids_mapping(channel_state) do
components = get_state_components(channel_state)

defp children_cids_mapping(%{components: components}) do
components
|> get_base_parent_cids_mapping()
|> fill_parent_cids_mapping(components)
|> reverse_mapping()
end

defp get_state_components(%{components: {components, _, _}}), do: components

defp get_base_parent_cids_mapping(components) do
components
|> Enum.map(fn {cid, _} -> {cid, nil} end)
|> Enum.map(fn %{cid: cid} -> {cid, nil} end)
|> Enum.into(%{})
end

defp fill_parent_cids_mapping(base_parent_cids_mapping, components) do
Enum.reduce(components, base_parent_cids_mapping, fn {cid, element}, parent_cids_mapping ->
{_, _, _, info, _} = element
children_cids = info.children_cids

Enum.reduce(children_cids, parent_cids_mapping, fn child_cid, parent_cids ->
%{parent_cids | child_cid => cid}
Enum.reduce(components, base_parent_cids_mapping, fn element, parent_cids_mapping ->
Enum.reduce(element.children_cids, parent_cids_mapping, fn child_cid, parent_cids ->
%{parent_cids | child_cid => element.cid}
end)
end)
end
Expand Down
114 changes: 114 additions & 0 deletions lib/live_debugger/services/live_view_debug_service.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
defmodule LiveDebugger.Services.LiveViewDebugService do
@moduledoc """
This module provides wrappers for Phoenix LiveView functions that are used for debugging a LiveView processes.
"""
@type lv() :: %{
pid: pid(),
view: module(),
topic: String.t(),
transport_pid: pid()
}

@callback list_liveviews() :: [lv()]
@callback socket(pid()) :: Phoenix.LiveView.Socket.t()
@callback live_components(pid()) :: [map()]

@spec list_liveviews() :: [lv()]
def list_liveviews() do
impl().list_liveviews()
end

@spec socket(pid()) :: Phoenix.LiveView.Socket.t()
def socket(lv_pid) do
impl().socket(lv_pid)
end

@spec live_components(pid()) :: [map()]
def live_components(lv_pid) do
impl().live_components(lv_pid)
end

defp impl() do
Application.get_env(
:live_debugger,
:liveview_service,
__MODULE__.Impl
)
end

defmodule Impl do
@moduledoc false
@behaviour LiveDebugger.Services.LiveViewDebugService

if LiveDebugger.Services.System.ModuleService.loaded?(Phoenix.LiveView.Debug) do
@impl true
defdelegate list_liveviews(), to: Phoenix.LiveView.Debug
@impl true
defdelegate socket(pid), to: Phoenix.LiveView.Debug
@impl true
defdelegate live_components(pid), to: Phoenix.LiveView.Debug
@impl true
defdelegate liveview?(pid), do: Phoenix.LiveView.Debug
else
alias LiveDebugger.Services.System.ProcessService

@impl true
def list_liveviews() do
ProcessService.list()
|> Enum.filter(fn pid -> ProcessService.initial_call(pid) |> liveview?() end)
|> Enum.map(fn pid ->
case LiveDebugger.Services.System.ProcessService.state(pid) do
{:ok, %{socket: socket, topic: topic}} ->
%{
pid: pid,
view: socket.view,
topic: topic,
transport_pid: socket.transport_pid
}

{:error, _} ->
nil
end
end)
|> Enum.reject(&is_nil/1)
end

@impl true
def socket(pid) do
case LiveDebugger.Services.System.ProcessService.state(pid) do
{:ok, %{socket: socket}} -> {:ok, socket}
{:error, _} -> {:error, :not_alive_or_not_a_liveview}
end
end

@impl true
def live_components(pid) do
case LiveDebugger.Services.System.ProcessService.state(pid) do
{:ok, %{components: {components, _, _}}} ->
component_info =
Enum.map(components, fn {cid, {mod, id, assigns, private, _prints}} ->
%{
id: id,
cid: cid,
module: mod,
assigns: assigns,
children_cids: private.children_cids
}
end)

{:ok, component_info}

{:error, _} ->
{:error, :not_alive_or_not_a_liveview}
end
end

@spec liveview?(initial_call :: mfa() | nil | {}) :: boolean()
defp liveview?(initial_call) when initial_call not in [nil, {}] do
elem(initial_call, 1) == :mount
end

defp liveview?(_), do: false
end
end
end
16 changes: 3 additions & 13 deletions lib/live_debugger/services/live_view_discovery_service.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryService do
@moduledoc """
This module provides functions that discovers LiveView processes in the debugged application.
"""
alias LiveDebugger.Services.System.ProcessService
alias LiveDebugger.Structs.LvProcess

@doc """
Expand Down Expand Up @@ -164,11 +163,9 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryService do
"""
@spec lv_processes() :: [LvProcess.t()]
def lv_processes() do
ProcessService.list()
|> Enum.reject(&(&1 == self()))
|> Enum.map(&{&1, ProcessService.initial_call(&1)})
|> Enum.filter(fn {_, initial_call} -> liveview?(initial_call) end)
|> Enum.map(fn {pid, _} -> LvProcess.new(pid) end)
LiveDebugger.Services.LiveViewDebugService.list_liveviews()
|> Enum.reject(&(&1.pid == self()))
|> Enum.map(&LvProcess.new(&1.pid))
|> Enum.reject(&is_nil/1)
end

Expand All @@ -195,11 +192,4 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryService do
end)
|> List.flatten()
end

@spec liveview?(initial_call :: mfa() | nil | {}) :: boolean()
defp liveview?(initial_call) when initial_call not in [nil, {}] do
elem(initial_call, 1) == :mount
end

defp liveview?(_), do: false
end
8 changes: 4 additions & 4 deletions lib/live_debugger/structs/lv_process.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule LiveDebugger.Structs.LvProcess do
@moduledoc """
This module provides a struct to represent a LiveView process.
It uses `LiveDebugger.Services.System.ProcessService` to fetch process state
It uses `LiveDebugger.Services.LiveViewDebugService` to fetch LiveView process state

* nested? - whether the process is a nested LiveView process
* debugger? - whether the process is a LiveDebugger process
Expand Down Expand Up @@ -58,12 +58,12 @@ defmodule LiveDebugger.Structs.LvProcess do
end

@doc """
Creates new LvProcess struct with the given `pid` by fetching the socket from the process state.
Creates new LvProcess struct with the given `pid` by fetching the socket from `LiveViewDebugService`.
"""
@spec new(pid :: pid()) :: t() | nil
def new(pid) do
case LiveDebugger.Services.System.ProcessService.state(pid) do
{:ok, %{socket: socket}} ->
case LiveDebugger.Services.LiveViewDebugService.socket(pid) do
{:ok, socket} ->
new(pid, socket)

{:error, _} ->
Expand Down
12 changes: 6 additions & 6 deletions lib/live_debugger/structs/tree_node.ex
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@ defmodule LiveDebugger.Structs.TreeNode do
{:ok, t() | nil} | {:error, term()}
def live_component_node(channel_state, cid)

def live_component_node(%{components: {components_map, _, _}}, cid) do
components_map
|> Enum.find(fn {integer_cid, _} -> integer_cid == cid.cid end)
def live_component_node(%{components: components}, cid) do
components
|> Enum.find(fn %{cid: integer_cid} -> integer_cid == cid.cid end)
|> case do
nil ->
{:ok, nil}
Expand All @@ -148,8 +148,8 @@ defmodule LiveDebugger.Structs.TreeNode do
{:ok, [t()]} | {:error, term()}
def live_component_nodes(channel_state)

def live_component_nodes(%{components: {components_map, _, _}}) do
Enum.reduce_while(components_map, {:ok, []}, fn channel_component, acc ->
def live_component_nodes(%{components: components}) do
Enum.reduce_while(components, {:ok, []}, fn channel_component, acc ->
case parse_channel_live_component(channel_component) do
{:ok, component} ->
{:ok, acc_components} = acc
Expand All @@ -163,7 +163,7 @@ defmodule LiveDebugger.Structs.TreeNode do

def live_component_nodes(_), do: {:error, :invalid_channel_state}

defp parse_channel_live_component({integer_cid, {module, id, assigns, _, _}}) do
defp parse_channel_live_component(%{cid: integer_cid, module: module, id: id, assigns: assigns}) do
{:ok,
%LiveComponentNode{
id: id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defmodule LiveDebuggerWeb.Live.Nested.NodeInspectorSidebarLive do

import LiveDebuggerWeb.Helpers.NestedLiveViewHelper

alias LiveDebugger.Services.System.ProcessService
alias LiveDebugger.Structs.TreeNode
alias LiveDebugger.Structs.LvProcess
alias LiveDebugger.Structs.Trace
Expand Down Expand Up @@ -371,7 +372,7 @@ defmodule LiveDebuggerWeb.Live.Nested.NodeInspectorSidebarLive do
end

defp send_event(pid, event, payload \\ %{}) do
{:ok, state} = ChannelService.state(pid)
{:ok, state} = ProcessService.state(pid)

message = %Message{
topic: state.topic,
Expand Down
12 changes: 7 additions & 5 deletions test/gen_servers/state_server_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule LiveDebugger.GenServers.StateServerTest do
alias LiveDebugger.Utils.PubSub, as: PubSubUtils
alias LiveDebugger.GenServers.StateServer
alias LiveDebugger.MockPubSubUtils
alias LiveDebugger.MockProcessService
alias LiveDebugger.MockLiveViewDebugService

setup :verify_on_exit!

Expand Down Expand Up @@ -49,8 +49,9 @@ defmodule LiveDebugger.GenServers.StateServerTest do

state = Fakes.state()

MockProcessService
|> expect(:state, fn ^pid -> {:ok, state} end)
MockLiveViewDebugService
|> expect(:socket, fn ^pid -> {:ok, state.socket} end)
|> expect(:live_components, fn ^pid -> {:ok, state.components} end)

MockPubSubUtils
|> expect(:broadcast, fn ^state_changed_node_topic, {:state_changed, ^state, ^trace} ->
Expand Down Expand Up @@ -80,8 +81,9 @@ defmodule LiveDebugger.GenServers.StateServerTest do

state = Fakes.state()

MockProcessService
|> expect(:state, fn ^pid -> {:ok, state} end)
MockLiveViewDebugService
|> expect(:socket, fn ^pid -> {:ok, state.socket} end)
|> expect(:live_components, fn ^pid -> {:ok, state.components} end)

MockPubSubUtils
|> expect(:broadcast, fn ^state_changed_node_topic, {:state_changed, ^state, ^trace} ->
Expand Down
Loading
Loading