Skip to content

Commit 4dd6772

Browse files
authored
Feature: Add streams section (#817)
* add stream section * add debugging streams with streams * fix deleting elements at initial render * Update elixir_display.ex * add config for new streams in update * better displaying of streams * update streams ui * fix css and initial stream bug * Update stream_utils.ex * Add tests * Update tests * fix formatting * add e2e test * Add suport for old live view versions
1 parent 1d1c01f commit 4dd6772

File tree

17 files changed

+1284
-2
lines changed

17 files changed

+1284
-2
lines changed

dev/components.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ defmodule LiveDebuggerDev.Components do
99
{"/embedded", "Embedded"},
1010
{"/embedded_in_controller", "EmbeddedInController"},
1111
{"/endless_crash_reload", "EndlessCrashReload"},
12+
{"/stream", "Stream"},
1213
{"/async_demo", "AsyncDemo"}
1314
]
1415

dev/live_components/stream.ex

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
defmodule LiveDebuggerDev.LiveComponents.StreamComponent do
2+
use DevWeb, :live_component
3+
4+
def update(assigns, socket) do
5+
items = [
6+
%{id: "item-1", name: "first"},
7+
%{id: "item-2", name: "second"},
8+
%{id: "item-3", name: "third"}
9+
]
10+
11+
socket
12+
|> assign(assigns)
13+
|> stream(:component_items, items)
14+
|> then(&{:ok, &1})
15+
end
16+
17+
def render(assigns) do
18+
~H"""
19+
<div>
20+
<.box title="Component with stream" color="yellow"></.box>
21+
</div>
22+
"""
23+
end
24+
end

dev/live_views/stream.ex

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
defmodule LiveDebuggerDev.LiveViews.Stream do
2+
use DevWeb, :live_view
3+
4+
alias LiveDebuggerDev.LiveComponents
5+
6+
@impl true
7+
def mount(_params, _session, socket) do
8+
socket =
9+
socket
10+
|> stream_configure(:items, dom_id: &"item-#{&1.id}")
11+
|> stream_configure(:another_items, dom_id: &"another-#{&1.id}")
12+
|> assign(:current_id, 0)
13+
|> assign(:another_items_id, 0)
14+
|> stream(:items, [])
15+
|> stream(:another_items, [])
16+
17+
{:ok, socket}
18+
end
19+
20+
@impl true
21+
def render(assigns) do
22+
~H"""
23+
<.box title="Items Stream options">
24+
<.button id="create-item" phx-click="create_item">Create Item</.button>
25+
<.button id="update-item" phx-click="update_item">Update Last Item</.button>
26+
<.button id="insert-many" phx-click="insert_many">Insert Many (Reverse Order)</.button>
27+
<.button id="insert-at-index" phx-click="insert_at_index">Insert At Index 4</.button>
28+
<.button id="delete-item" phx-click="delete_item">Delete Last</.button>
29+
<.button id="reset-items" phx-click="reset_items">Reset Stream</.button>
30+
<.button id="add-new-stream" phx-click="add_new_stream">Add new stream</.button>
31+
<.button id="delete-both-last" phx-click="delete_both_last">
32+
Delete Last From Both Streams
33+
</.button>
34+
</.box>
35+
36+
<hr />
37+
38+
<.box title="Items Stream">
39+
<ul id="item-list" phx-update="stream">
40+
<li :for={{id, item} <- @streams.items} id={id}>
41+
<strong>ID:</strong> <%= item.id %>, <strong>Number:</strong> <%= item.number %>
42+
<button phx-click="delete_by_dom_id" phx-value-id={id}>X</button>
43+
</li>
44+
</ul>
45+
</.box>
46+
47+
<hr />
48+
49+
<.box title="Another Items stream options">
50+
<.button id="create-another-item" phx-click="create_another_item">Create Another Item</.button>
51+
</.box>
52+
53+
<.box title="Another Items Stream">
54+
<ul id="another-items-list" phx-update="stream">
55+
<li :for={{id, item} <- @streams.another_items} id={id}>
56+
<strong><%= item.id %></strong> -><%= item.number %>
57+
</li>
58+
</ul>
59+
</.box>
60+
61+
<hr />
62+
63+
<.live_component id="stream_component" module={LiveComponents.StreamComponent} />
64+
"""
65+
end
66+
67+
@impl true
68+
def handle_event("create_item", _params, socket) do
69+
next_id = socket.assigns.current_id
70+
item = %{id: next_id, number: Enum.random(1..1000)}
71+
72+
socket =
73+
socket
74+
|> stream_insert(:items, item, at: 0)
75+
|> assign(:current_id, next_id + 1)
76+
77+
{:noreply, socket}
78+
end
79+
80+
@impl true
81+
def handle_event("update_item", _params, socket) do
82+
id_to_update = socket.assigns.current_id - 1
83+
84+
if id_to_update >= 0 do
85+
updated_item = %{id: id_to_update, number: Enum.random(1001..2000)}
86+
87+
socket =
88+
socket
89+
|> stream_insert(:items, updated_item, update_only: true)
90+
91+
{:noreply, socket}
92+
else
93+
{:noreply, socket}
94+
end
95+
end
96+
97+
@impl true
98+
def handle_event("insert_many", _params, socket) do
99+
start_id = socket.assigns.current_id
100+
101+
items =
102+
Enum.map(0..2, fn i ->
103+
%{id: start_id + i, number: Enum.random(1..99)}
104+
end)
105+
106+
socket =
107+
socket
108+
|> stream(:items, Enum.reverse(items), at: -1)
109+
|> assign(:current_id, start_id + length(items))
110+
111+
{:noreply, socket}
112+
end
113+
114+
@impl true
115+
def handle_event("insert_at_index", _params, socket) do
116+
next_id = socket.assigns.current_id
117+
item = %{id: next_id, number: 9999}
118+
119+
socket =
120+
socket
121+
|> stream_insert(:items, item, at: 4)
122+
|> assign(:current_id, next_id + 1)
123+
124+
{:noreply, socket}
125+
end
126+
127+
@impl true
128+
def handle_event("delete_item", _params, socket) do
129+
last_id = socket.assigns.current_id - 1
130+
131+
if last_id >= 0 do
132+
socket =
133+
socket
134+
|> stream_delete(:items, %{id: last_id})
135+
|> assign(:current_id, last_id)
136+
137+
{:noreply, socket}
138+
else
139+
{:noreply, socket}
140+
end
141+
end
142+
143+
@impl true
144+
def handle_event("delete_by_dom_id", %{"id" => dom_id}, socket) do
145+
socket = stream_delete_by_dom_id(socket, :items, dom_id)
146+
{:noreply, socket}
147+
end
148+
149+
@impl true
150+
def handle_event("reset_items", _params, socket) do
151+
socket =
152+
socket
153+
|> stream(:items, [], reset: true)
154+
|> assign(:current_id, 0)
155+
156+
{:noreply, socket}
157+
end
158+
159+
@impl true
160+
def handle_event("create_another_item", _params, socket) do
161+
next_id = socket.assigns.another_items_id
162+
item = %{id: next_id, number: Enum.random(1..100)}
163+
164+
socket =
165+
socket
166+
|> stream_insert(:another_items, item, at: -1)
167+
|> assign(:another_items_id, next_id + 1)
168+
169+
{:noreply, socket}
170+
end
171+
172+
@impl true
173+
def handle_event("delete_both_last", _params, socket) do
174+
last_item_id = socket.assigns.current_id - 1
175+
last_another_id = socket.assigns.another_items_id - 1
176+
177+
socket =
178+
socket
179+
|> then(fn s ->
180+
if last_item_id >= 0 do
181+
stream_delete(s, :items, %{id: last_item_id})
182+
else
183+
s
184+
end
185+
end)
186+
|> then(fn s ->
187+
if last_another_id >= 0 do
188+
stream_delete(s, :another_items, %{id: last_another_id})
189+
else
190+
s
191+
end
192+
end)
193+
|> assign(:current_id, max(last_item_id, 0))
194+
|> assign(:another_items_id, max(last_another_id, 0))
195+
196+
{:noreply, socket}
197+
end
198+
199+
@impl true
200+
def handle_event("add_new_stream", _params, socket) do
201+
socket =
202+
socket
203+
|> stream(:new_items, [])
204+
205+
{:noreply, socket}
206+
end
207+
end

dev/router.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ defmodule LiveDebuggerDev.Router do
1818
live("/messages", LiveDebuggerDev.LiveViews.Messages)
1919
live("/embedded", LiveDebuggerDev.LiveViews.Embedded)
2020
live("/endless_crash_reload", LiveDebuggerDev.LiveViews.EndlessCrashReload)
21+
live("/stream", LiveDebuggerDev.LiveViews.Stream)
2122
live("/async_demo", LiveDebuggerDev.LiveViews.AsyncDemo)
2223
get("/embedded_in_controller", LiveDebuggerDev.EmbeddedLiveViewController, :embedded)
2324
end
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
defmodule LiveDebugger.App.Debugger.Streams.Actions do
2+
@moduledoc """
3+
Actions for `LiveDebugger.App.Debugger.Streams` context.
4+
"""
5+
6+
alias LiveDebugger.App.Debugger.Streams.StreamUtils
7+
8+
@type stream_update_result :: %{
9+
functions: [function()],
10+
config: [function()],
11+
name: atom()
12+
}
13+
14+
@spec update_stream(
15+
stream_updates :: StreamUtils.live_stream_item(),
16+
dom_id_fun :: (any() -> String.t())
17+
) ::
18+
{:ok, stream_update_result()} | {:error, String.t()}
19+
def update_stream(stream_updates, dom_id_fun) do
20+
with name <- stream_updates.name,
21+
funs <- StreamUtils.stream_update_functions(stream_updates),
22+
config <-
23+
StreamUtils.stream_config(stream_updates, dom_id: dom_id_fun) do
24+
{:ok, %{functions: funs, config: config, name: name}}
25+
end
26+
end
27+
end
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
defmodule LiveDebugger.App.Debugger.Streams.Queries do
2+
@moduledoc """
3+
Queries for `LiveDebugger.App.Debugger.Streams` context.
4+
"""
5+
6+
alias LiveDebugger.API.TracesStorage
7+
8+
alias LiveDebugger.App.Debugger.Streams.StreamUtils
9+
10+
require Logger
11+
12+
@type streams_result :: %{
13+
functions: [function()],
14+
config: map(),
15+
names: [atom()]
16+
}
17+
18+
@spec fetch_streams_from_render_traces(pid :: pid(), node_id :: TreeNode.id()) ::
19+
{:ok, streams_result()} | {:error, String.t()}
20+
def fetch_streams_from_render_traces(pid, node_id) do
21+
with {:ok, render_traces} <- fetch_render_traces(pid, node_id),
22+
stream_traces <- StreamUtils.extract_stream_traces(render_traces),
23+
names <- StreamUtils.streams_names(stream_traces),
24+
funs <- StreamUtils.streams_functions(stream_traces, names),
25+
config <- StreamUtils.streams_config(stream_traces, names) do
26+
{:ok, %{functions: funs, config: config, names: names}}
27+
else
28+
:end_of_table ->
29+
:end_of_table
30+
31+
error ->
32+
Logger.error("Failed to fetch streams: #{inspect(error)}")
33+
{:error, "Failed to fetch streams"}
34+
end
35+
end
36+
37+
defp fetch_render_traces(pid, node_id) do
38+
case TracesStorage.get!(pid, functions: ["render/1"], node_id: node_id) do
39+
:end_of_table ->
40+
:end_of_table
41+
42+
{stream_updates, _trace} ->
43+
{:ok, stream_updates}
44+
end
45+
end
46+
end

0 commit comments

Comments
 (0)