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
2 changes: 2 additions & 0 deletions assets/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import TraceLabelSearchHighlight from './hooks/trace_label_search_highlight';
import AssignsBodySearchHighlight from './hooks/assigns_body_search_highlight';
import DiffPulse from './hooks/diff_pulse';
import ChartHook from './hooks/chart_hook';
import CollapsedSectionPulse from './hooks/collapsed_section_pulse';

import topbar from './vendor/topbar';

Expand Down Expand Up @@ -42,6 +43,7 @@ function createHooks() {
AssignsBodySearchHighlight,
DiffPulse,
ChartHook,
CollapsedSectionPulse,
};
}

Expand Down
17 changes: 17 additions & 0 deletions assets/app/hooks/collapsed_section_pulse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const CollapsedSectionPulse = {
mounted() {
const collapsibleEl = this.el.closest('details');

this.handleEvent(`${this.el.id}-pulse`, () => {
if (collapsibleEl.open) return;

collapsibleEl.classList.add('animate-section-pulse');

setTimeout(() => {
collapsibleEl.classList.remove('animate-section-pulse');
}, 500);
});
},
};

export default CollapsedSectionPulse;
37 changes: 37 additions & 0 deletions assets/app/hooks/collapsible.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,46 @@
function setOpen(el) {
if (el.dataset.saveStateInBrowser === 'true') {
setOpenFromLocalStorage(el);
return;
}

if (el.dataset.open === 'true') {
el.open = true;
} else {
el.open = false;
}
}

function setOpenFromLocalStorage(el) {
const open_state = localStorage.getItem(`collapsible-open-${el.id}`);

if (open_state !== null) {
el.open = open_state === 'true';
return;
}

if (el.dataset.open === 'true') {
el.open = true;
localStorage.setItem(`collapsible-open-${el.id}`, 'true');
} else {
el.open = false;
localStorage.setItem(`collapsible-open-${el.id}`, 'false');
}
}

function maybeSaveStateOnChange(el) {
if (el.dataset.saveStateInBrowser === 'true') {
el.addEventListener('toggle', () => {
localStorage.setItem(`collapsible-open-${el.id}`, el.open.toString());
});

window.addEventListener('storage', ({ key }) => {
if (key !== `collapsible-open-${el.id}`) return;
setOpenFromLocalStorage(el);
});
}
}

function handleCollapsibleEvent(payload, el) {
if (payload.action === 'toggle') {
el.open = !el.open;
Expand All @@ -24,6 +59,8 @@ const Collapsible = {
mounted() {
setOpen(this.el);

maybeSaveStateOnChange(this.el);

this.handleEvent(`${this.el.id}-collapsible`, (payload) => {
handleCollapsibleEvent(payload, this.el);
});
Expand Down
9 changes: 9 additions & 0 deletions assets/app/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,22 @@ module.exports = {
},
'100%': { backgroundColor: '', color: '' },
},
sectionPulse: {
'0%': {
boxShadow: '0 0 10px var(--diff-pulse-bg)',
},
'100%': {
boxShadow: '0 0 0px var(--diff-pulse-bg)',
},
},
},
animation: {
'fade-out': 'fadeOut 200ms ease-out forwards',
'fade-out-mobile': 'fadeOutMobile 200ms ease-out forwards',
'fade-in': 'fadeIn 100ms ease-in forwards',
'fade-in-mobile': 'fadeInMobile 100ms ease-in forwards',
'diff-pulse': 'diffPulse 500ms ease-out',
'section-pulse': 'sectionPulse 500ms ease-out',
},
},
},
Expand Down
22 changes: 20 additions & 2 deletions lib/live_debugger/app/debugger/async_jobs/web/async_jobs_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ defmodule LiveDebugger.App.Debugger.AsyncJobs.Web.AsyncJobsLive do

alias LiveDebugger.App.Debugger.AsyncJobs.Queries, as: AsyncJobsQueries

@async_jobs_sectinon_id "async-jobs"

@doc """
Renders the `AsyncJobsLive` as a nested LiveView component.

Expand Down Expand Up @@ -72,9 +74,17 @@ defmodule LiveDebugger.App.Debugger.AsyncJobs.Web.AsyncJobsLive do

@impl true
def render(assigns) do
assigns = assign(assigns, id: @async_jobs_sectinon_id)

~H"""
<div class="max-w-full flex flex-1">
<.section title="Async jobs" id="async-jobs" inner_class="mx-0 p-4" class="flex-1">
<.collapsible_section
title="Async jobs"
id={@id}
inner_class="mx-0 p-4"
class="flex-1"
save_state_in_browser={true}
>
<div class="w-full h-full flex flex-col gap-2">
<.async_result :let={async_jobs} assign={@async_jobs}>
<:failed>
Expand All @@ -90,7 +100,7 @@ defmodule LiveDebugger.App.Debugger.AsyncJobs.Web.AsyncJobsLive do
/>
</.async_result>
</div>
</.section>
</.collapsible_section>
</div>
"""
end
Expand All @@ -113,6 +123,7 @@ defmodule LiveDebugger.App.Debugger.AsyncJobs.Web.AsyncJobsLive do

socket
|> assign(:async_jobs, AsyncResult.ok(updated_async_jobs))
|> push_event("#{@async_jobs_sectinon_id}-summary-pulse", %{})
|> noreply()
end

Expand All @@ -137,6 +148,12 @@ defmodule LiveDebugger.App.Debugger.AsyncJobs.Web.AsyncJobsLive do
def handle_info(_, socket), do: {:noreply, socket}

@impl true
def handle_async(:fetch_async_jobs, {:ok, {:ok, []}}, socket) do
socket
|> assign(:async_jobs, AsyncResult.ok([]))
|> noreply()
end

def handle_async(:fetch_async_jobs, {:ok, {:ok, async_jobs}}, socket)
when is_list(async_jobs) do
Enum.each(async_jobs, fn async_job ->
Expand All @@ -145,6 +162,7 @@ defmodule LiveDebugger.App.Debugger.AsyncJobs.Web.AsyncJobsLive do

socket
|> assign(:async_jobs, AsyncResult.ok(async_jobs))
|> push_event("#{@async_jobs_sectinon_id}-summary-pulse", %{})
|> noreply()
end

Expand Down
20 changes: 12 additions & 8 deletions lib/live_debugger/app/debugger/streams/web/components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,22 @@ defmodule LiveDebugger.App.Debugger.Streams.Web.Components do
"""
end

attr(:id, :string, required: true)
slot(:display, required: true)

def streams_section(assigns) do
~H"""
<div id="streams_section-container">
<.section id="streams" class="h-max overflow-y-hidden" title="Streams">
<:right_panel>
<.streams_info_tooltip id="stream-info" />
</:right_panel>
<%= render_slot(@display) %>
</.section>
</div>
<.collapsible_section
id={@id}
class="h-max overflow-y-hidden"
title="Streams"
save_state_in_browser={true}
>
<:right_panel>
<.streams_info_tooltip id="stream-info" />
</:right_panel>
<%= render_slot(@display) %>
</.collapsible_section>
"""
end

Expand Down
7 changes: 6 additions & 1 deletion lib/live_debugger/app/debugger/streams/web/streams_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ defmodule LiveDebugger.App.Debugger.Streams.Web.StreamsLive do
alias LiveDebugger.Services.CallbackTracer.Events.StreamUpdated
alias LiveDebugger.App.Debugger.Events.NodeIdParamChanged

@streams_section_id "streams-section-container"

@doc """
Renders the `StreamsLive` as a nested LiveView component.

Expand Down Expand Up @@ -67,9 +69,11 @@ defmodule LiveDebugger.App.Debugger.Streams.Web.StreamsLive do

@impl true
def render(assigns) do
assigns = assign(assigns, id: @streams_section_id)

~H"""
<div class="flex-1 max-w-full flex flex-col gap-4">
<StreamsComponents.streams_section>
<StreamsComponents.streams_section id={@id}>
<:display>
<.async_result :let={stream_names} assign={@stream_names}>
<:loading>
Expand Down Expand Up @@ -112,6 +116,7 @@ defmodule LiveDebugger.App.Debugger.Streams.Web.StreamsLive do
) do
socket
|> Hooks.Streams.assign_async_streams(stream, dom_id_fun)
|> push_event("#{@streams_section_id}-summary-pulse", %{})
|> noreply()
end

Expand Down
55 changes: 54 additions & 1 deletion lib/live_debugger/app/web/components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ defmodule LiveDebugger.App.Web.Components do
attr(:chevron_class, :any, default: nil, doc: "CSS class for the chevron icon")
attr(:open, :boolean, default: false, doc: "Whether the collapsible is open by default")

attr(:save_state_in_browser, :boolean,
default: false,
doc: "Whether to save the open/closed state in the browser's local storage"
)

attr(:icon, :string,
default: "icon-chevron-right",
doc: "Icon for chevron. It will be rotated 90 degrees when the collapsible is open"
Expand All @@ -140,13 +145,17 @@ defmodule LiveDebugger.App.Web.Components do
slot(:inner_block, required: true)

def collapsible(assigns) do
assigns = assign(assigns, :open, to_string(assigns.open))
assigns =
assigns
|> assign(:open, to_string(assigns.open))
|> assign(:save_state_in_browser, to_string(assigns.save_state_in_browser))

~H"""
<details
id={@id}
phx-hook="Collapsible"
data-open={@open}
data-save-state-in-browser={@save_state_in_browser}
class={[
"block"
| List.wrap(@class)
Expand Down Expand Up @@ -327,6 +336,50 @@ defmodule LiveDebugger.App.Web.Components do
"""
end

attr(:id, :string, required: true)
attr(:title, :string, required: true)

attr(:save_state_in_browser, :boolean,
default: false,
doc: "Whether to save the open/closed state in the browser's local storage"
)

attr(:class, :any, default: nil)
attr(:title_class, :any, default: nil)
attr(:inner_class, :any, default: nil)

slot(:right_panel)
slot(:inner_block)

def collapsible_section(assigns) do
~H"""
<.collapsible
id={@id}
phx-hook="CollapsedSectionPulse"
class={[
"w-full min-w-[20rem] flex flex-col shadow-custom rounded-sm bg-surface-0-bg border border-default-border"
| List.wrap(@class)
]}
label_class="pr-4 flex items-center h-12 p-2 border-b border-default-border"
save_state_in_browser={@save_state_in_browser}
>
<:label>
<div class="ml-1 flex justify-between items-center w-full gap-2">
<div class={["font-medium text-sm min-w-26" | List.wrap(@title_class)]}><%= @title %></div>
<div class="w-max">
<%= render_slot(@right_panel) %>
</div>
</div>
</:label>
<div class={[
"flex flex-1 overflow-auto rounded-sm bg-surface-0-bg" | List.wrap(@inner_class)
]}>
<%= render_slot(@inner_block) %>
</div>
</.collapsible>
"""
end

@doc """
Typography component to render headings.
"""
Expand Down
2 changes: 1 addition & 1 deletion priv/static/app.css

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions priv/static/app.js

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions priv/static/app.js.map

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions test/e2e/async_jobs_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ defmodule LiveDebugger.E2E.AsyncJobsTest do
:ok
end

setup %{sessions: [_dev_app, debugger]} do
debugger
|> visit("/")
|> set_collapsible_open_state("async-jobs", "true")

:ok
end

@sessions 2
feature "user can see and track async jobs in LiveView and LiveComponent", %{
sessions: [dev_app, debugger]
Expand Down Expand Up @@ -104,4 +112,14 @@ defmodule LiveDebugger.E2E.AsyncJobsTest do
do: css("#node-inspector-basic-info-current-node-module", text: text)

defp component_tree_node(cid), do: css("#button-tree-node-#{cid}-components-tree")

defp set_collapsible_open_state(debugger, section_id, state) do
debugger
|> execute_script(
"""
localStorage.setItem(`collapsible-open-${arguments[0]}`, arguments[1])
""",
[section_id, state]
)
end
end
Loading