Skip to content

Commit 53a8942

Browse files
authored
Enhancement: Use telemetry event for updating components tree (#926)
* Add service for handling LiveView telemetry * Bump :phoenix_live_view * Fix * Refactor * Add e2e test * Remove redundant tests * Extract :phoenix_live_view version checks * Remove unused functions * If out logic that changes based on LiveView version * Fix alias
1 parent 4f4f049 commit 53a8942

File tree

20 files changed

+480
-387
lines changed

20 files changed

+480
-387
lines changed

dev/embedded_live_view_controller.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
defmodule LiveDebuggerDev.EmbeddedLiveViewController do
2-
use Phoenix.Controller, layouts: [html: {LiveDebuggerDev.Layout, :embedded}]
2+
use Phoenix.Controller,
3+
layouts: [html: {LiveDebuggerDev.Layout, :embedded}],
4+
formats: []
35

46
import Phoenix.LiveView.Controller
57

e2e/tests/dev-dbg-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const test = base
2121
},
2222
});
2323

24-
export const assigns_entry = (page: Page, key: string, value: string) =>
24+
export const findAssignsEntry = (page: Page, key: string, value: string) =>
2525
page.locator(
2626
`xpath=//*[@id=\"assigns\"]//*[contains(normalize-space(text()), \"${key}:\")]/../..//*[contains(normalize-space(text()), \"${value}\")]`
2727
);

e2e/tests/node-inspector.spec.ts

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,25 @@ import {
22
test,
33
expect,
44
findTraces,
5-
assigns_entry,
5+
findAssignsEntry,
66
findSwitchTracingButton,
77
findRefreshTracesButton,
88
findClearTracesButton,
9+
Page,
910
} from './dev-dbg-test';
1011

12+
const findNodeBasicInfo = (page: Page) =>
13+
page.locator('#node-inspector-basic-info');
14+
15+
const findComponentsTreeButton = (page: Page, name: string) =>
16+
page.getByRole('button', { name });
17+
1118
test('user can see traces of executed callbacks and updated assigns', async ({
1219
devApp,
1320
dbgApp,
1421
}) => {
1522
const traces = findTraces(dbgApp);
16-
await expect(assigns_entry(dbgApp, 'counter', '0')).toBeVisible();
23+
await expect(findAssignsEntry(dbgApp, 'counter', '0')).toBeVisible();
1724
await expect(traces).toHaveCount(2);
1825

1926
const incBtn = devApp.getByRole('button', {
@@ -23,15 +30,15 @@ test('user can see traces of executed callbacks and updated assigns', async ({
2330
await incBtn.click();
2431
await incBtn.click();
2532

26-
await expect(assigns_entry(dbgApp, 'counter', '2')).toBeVisible();
33+
await expect(findAssignsEntry(dbgApp, 'counter', '2')).toBeVisible();
2734
await expect(traces).toHaveCount(6);
2835

2936
await findSwitchTracingButton(dbgApp).click();
3037

3138
await incBtn.click();
3239
await incBtn.click();
3340

34-
await expect(assigns_entry(dbgApp, 'counter', '4')).toBeVisible();
41+
await expect(findAssignsEntry(dbgApp, 'counter', '4')).toBeVisible();
3542
await expect(traces).toHaveCount(6);
3643

3744
await findRefreshTracesButton(dbgApp).click();
@@ -104,6 +111,49 @@ test('return button redirects to active live views dashboard', async ({
104111
).toBeVisible();
105112
});
106113

114+
test('user can change nodes using node tree and see their assigns and callback traces', async ({
115+
devApp,
116+
dbgApp,
117+
}) => {
118+
await findComponentsTreeButton(dbgApp, 'Conditional (5)').click();
119+
120+
await expect(findNodeBasicInfo(dbgApp)).toContainText('LiveComponent');
121+
await expect(findNodeBasicInfo(dbgApp)).toContainText(
122+
'LiveDebuggerDev.LiveComponents.Conditional'
123+
);
124+
125+
await expect(findAssignsEntry(dbgApp, 'show_child?', 'false')).toBeVisible();
126+
await expect(findTraces(dbgApp)).toHaveCount(2);
127+
128+
await expect(
129+
findComponentsTreeButton(dbgApp, 'ManyAssigns (15)')
130+
).not.toBeVisible();
131+
132+
await devApp.locator('#conditional-button').click();
133+
134+
await expect(
135+
findComponentsTreeButton(dbgApp, 'ManyAssigns (15)')
136+
).toBeVisible();
137+
138+
await expect(findAssignsEntry(dbgApp, 'show_child?', 'true')).toBeVisible();
139+
await expect(findTraces(dbgApp)).toHaveCount(4);
140+
141+
await findComponentsTreeButton(dbgApp, 'Conditional (6)').click();
142+
await findComponentsTreeButton(dbgApp, 'Conditional (5)').click();
143+
144+
await expect(findAssignsEntry(dbgApp, 'show_child?', 'true')).toBeVisible();
145+
await expect(findTraces(dbgApp)).toHaveCount(4);
146+
147+
await devApp.locator('#conditional-button').click();
148+
149+
await expect(
150+
findComponentsTreeButton(dbgApp, 'ManyAssigns (15)')
151+
).not.toBeVisible();
152+
153+
await expect(findAssignsEntry(dbgApp, 'show_child?', 'false')).toBeVisible();
154+
await expect(findTraces(dbgApp)).toHaveCount(6);
155+
});
156+
107157
test('Open in editor is disabled when envs are not set', async ({ dbgApp }) => {
108158
const openButton = dbgApp.getByRole('button', { name: 'Open in editor' });
109159
await expect(openButton).toBeDisabled();

lib/live_debugger/app/debugger/streams/stream_utils.ex

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ defmodule LiveDebugger.App.Debugger.Streams.StreamUtils do
33
Utilities for extracting Phoenix.LiveView.Stream diffs
44
from render traces and mapping them into a list of functions.
55
"""
6-
7-
@live_view_vsn Application.spec(:phoenix_live_view, :vsn) |> to_string()
6+
alias LiveDebugger.Utils.Versions
87

98
@type live_stream_item :: %Phoenix.LiveView.LiveStream{
109
name: atom(),
@@ -63,17 +62,9 @@ defmodule LiveDebugger.App.Debugger.Streams.StreamUtils do
6362
reset?: reset?,
6463
consumable?: _consumable?
6564
}) do
66-
# streams changed ordering in 1.0.2 so we don't need to reverse the inserts before this version
67-
inserts =
68-
if Version.match?(@live_view_vsn, ">= 1.0.2") do
69-
Enum.reverse(inserts)
70-
else
71-
inserts
72-
end
73-
7465
[]
7566
|> maybe_add_reset(reset?, name)
76-
|> maybe_add_inserts(inserts, name)
67+
|> maybe_add_inserts(adjust_inserts(inserts), name)
7768
|> maybe_add_deletes(deletes, name)
7869
end
7970

@@ -245,4 +236,10 @@ defmodule LiveDebugger.App.Debugger.Streams.StreamUtils do
245236
end
246237
end)
247238
end
239+
240+
if Versions.live_view_streams_order_changed?() do
241+
defp adjust_inserts(inserts), do: Enum.reverse(inserts)
242+
else
243+
defp adjust_inserts(inserts), do: inserts
244+
end
248245
end

lib/live_debugger/services.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ defmodule LiveDebugger.Services do
1111
{LiveDebugger.Services.GarbageCollector.Supervisor, []},
1212
{LiveDebugger.Services.ProcessMonitor.Supervisor, []},
1313
{LiveDebugger.Services.ClientCommunicator.Supervisor, []},
14-
{LiveDebugger.Services.SuccessorDiscoverer.Supervisor, []}
14+
{LiveDebugger.Services.SuccessorDiscoverer.Supervisor, []},
15+
{LiveDebugger.Services.TelemetryHandler.Supervisor, []}
1516
]
1617
end
1718
end

lib/live_debugger/services/callback_tracer/actions/function_trace.ex

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ defmodule LiveDebugger.Services.CallbackTracer.Actions.FunctionTrace do
33
This module provides actions for traces.
44
"""
55

6+
alias LiveDebugger.Utils.Versions
67
alias LiveDebugger.Structs.Trace.FunctionTrace
78
alias LiveDebugger.API.TracesStorage
8-
alias LiveDebugger.API.LiveViewDebug
9-
alias LiveDebugger.Utils.Modules, as: UtilsModules
109

1110
alias LiveDebugger.Bus
1211
alias LiveDebugger.Services.CallbackTracer.Events.TraceCalled
@@ -34,39 +33,41 @@ defmodule LiveDebugger.Services.CallbackTracer.Actions.FunctionTrace do
3433
end
3534
end
3635

37-
@spec create_delete_component_trace(
38-
n :: non_neg_integer(),
39-
args :: list(),
40-
pid :: pid(),
41-
cid :: String.t(),
42-
timestamp :: :erlang.timestamp()
43-
) :: {:ok, FunctionTrace.t()} | :live_debugger_trace | {:error, term()}
44-
def create_delete_component_trace(n, args, pid, cid, timestamp) do
45-
pid
46-
|> LiveViewDebug.socket()
47-
|> case do
48-
{:ok, %{id: socket_id, transport_pid: t_pid, view: view}} when is_pid(t_pid) ->
49-
if UtilsModules.debugger_module?(view) do
50-
:live_debugger_trace
51-
else
52-
trace =
53-
FunctionTrace.new(
54-
n,
55-
Phoenix.LiveView.Diff,
56-
:delete_component,
57-
args,
58-
pid,
59-
timestamp,
60-
socket_id: socket_id,
61-
transport_pid: t_pid,
62-
cid: %Phoenix.LiveComponent.CID{cid: cid}
63-
)
64-
65-
{:ok, trace}
66-
end
67-
68-
_ ->
69-
{:error, "Could not get socket"}
36+
if not Versions.live_component_destroyed_telemetry_supported?() do
37+
@spec create_delete_component_trace(
38+
n :: non_neg_integer(),
39+
args :: list(),
40+
pid :: pid(),
41+
cid :: String.t(),
42+
timestamp :: :erlang.timestamp()
43+
) :: {:ok, FunctionTrace.t()} | :live_debugger_trace | {:error, term()}
44+
def create_delete_component_trace(n, args, pid, cid, timestamp) do
45+
pid
46+
|> LiveDebugger.API.LiveViewDebug.socket()
47+
|> case do
48+
{:ok, %{id: socket_id, transport_pid: t_pid, view: view}} when is_pid(t_pid) ->
49+
if LiveDebugger.Utils.Modules.debugger_module?(view) do
50+
:live_debugger_trace
51+
else
52+
trace =
53+
FunctionTrace.new(
54+
n,
55+
Phoenix.LiveView.Diff,
56+
:delete_component,
57+
args,
58+
pid,
59+
timestamp,
60+
socket_id: socket_id,
61+
transport_pid: t_pid,
62+
cid: %Phoenix.LiveComponent.CID{cid: cid}
63+
)
64+
65+
{:ok, trace}
66+
end
67+
68+
_ ->
69+
{:error, "Could not get socket"}
70+
end
7071
end
7172
end
7273

lib/live_debugger/services/callback_tracer/actions/state.ex

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ defmodule LiveDebugger.Services.CallbackTracer.Actions.State do
33
Actions responsible for saving LiveView process' state.
44
"""
55

6+
alias LiveDebugger.Utils.Versions
67
alias LiveDebugger.API.StatesStorage
78
alias LiveDebugger.API.LiveViewDebug
89
alias LiveDebugger.Structs.Trace.FunctionTrace
@@ -42,8 +43,10 @@ defmodule LiveDebugger.Services.CallbackTracer.Actions.State do
4243
do_save_initial_state!(pid, socket)
4344
end
4445

45-
def maybe_save_state!(%FunctionTrace{pid: pid, function: :delete_component, type: :call}) do
46-
do_save_state!(pid)
46+
if not Versions.live_component_destroyed_telemetry_supported?() do
47+
def maybe_save_state!(%FunctionTrace{pid: pid, function: :delete_component, type: :call}) do
48+
do_save_state!(pid)
49+
end
4750
end
4851

4952
def maybe_save_state!(_), do: :ok

lib/live_debugger/services/callback_tracer/actions/tracing.ex

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ defmodule LiveDebugger.Services.CallbackTracer.Actions.Tracing do
44
"""
55
require Logger
66

7+
alias LiveDebugger.Utils.Versions
78
alias LiveDebugger.Services.CallbackTracer.GenServers.TracingManager
89
alias LiveDebugger.Services.CallbackTracer.Queries.Callbacks, as: CallbackQueries
910
alias LiveDebugger.Services.CallbackTracer.Queries.Traces, as: TraceQueries
@@ -92,7 +93,10 @@ defmodule LiveDebugger.Services.CallbackTracer.Actions.Tracing do
9293
defp apply_trace_patterns(modules) do
9394
# This is not a callback created by user
9495
# We trace it to refresh the components tree
95-
Dbg.trace_pattern({Phoenix.LiveView.Diff, :delete_component, 2}, [])
96+
# This will be replaced with telemetry event added in LiveView 1.1.0
97+
if not Versions.live_component_destroyed_telemetry_supported?() do
98+
Dbg.trace_pattern({Phoenix.LiveView.Diff, :delete_component, 2}, [])
99+
end
96100

97101
modules
98102
|> CallbackQueries.all_callbacks()

lib/live_debugger/services/callback_tracer/gen_servers/trace_handler.ex

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ defmodule LiveDebugger.Services.CallbackTracer.GenServers.TraceHandler do
55

66
use GenServer
77

8+
alias LiveDebugger.Utils.Versions
89
alias LiveDebugger.Utils.Callbacks, as: CallbackUtils
910
alias LiveDebugger.Services.CallbackTracer.Actions.FunctionTrace, as: TraceActions
1011
alias LiveDebugger.Services.CallbackTracer.Actions.State, as: StateActions
@@ -64,27 +65,28 @@ defmodule LiveDebugger.Services.CallbackTracer.GenServers.TraceHandler do
6465
#
6566
#########################################################
6667

67-
@impl true
68-
def handle_cast(
69-
{:new_trace,
70-
{_, pid, _, {Phoenix.LiveView.Diff, :delete_component, [cid | _] = args}, ts}, n},
71-
state
72-
) do
73-
Task.start(fn ->
74-
with {:ok, trace} <- TraceActions.create_delete_component_trace(n, args, pid, cid, ts),
75-
:ok <- StateActions.maybe_save_state!(trace),
76-
:ok <- TraceActions.publish_trace(trace) do
77-
:ok
78-
else
79-
:live_debugger_trace ->
68+
if not Versions.live_component_destroyed_telemetry_supported?() do
69+
def handle_cast(
70+
{:new_trace,
71+
{_, pid, _, {Phoenix.LiveView.Diff, :delete_component, [cid | _] = args}, ts}, n},
72+
state
73+
) do
74+
Task.start(fn ->
75+
with {:ok, trace} <- TraceActions.create_delete_component_trace(n, args, pid, cid, ts),
76+
:ok <- StateActions.maybe_save_state!(trace),
77+
:ok <- TraceActions.publish_trace(trace) do
8078
:ok
79+
else
80+
:live_debugger_trace ->
81+
:ok
8182

82-
{:error, err} ->
83-
raise "Error while handling trace: #{inspect(err)}"
84-
end
85-
end)
83+
{:error, err} ->
84+
raise "Error while handling trace: #{inspect(err)}"
85+
end
86+
end)
8687

87-
{:noreply, state}
88+
{:noreply, state}
89+
end
8890
end
8991

9092
#########################################################
@@ -97,6 +99,7 @@ defmodule LiveDebugger.Services.CallbackTracer.GenServers.TraceHandler do
9799
#
98100
#########################################################
99101

102+
@impl true
100103
def handle_cast({:new_trace, {_, pid, :call, {module, fun, args}, ts}, n}, state)
101104
when fun in @allowed_callbacks do
102105
with {:ok, trace} <- TraceActions.create_trace(n, module, fun, args, pid, ts),

0 commit comments

Comments
 (0)