fix: fixed CSM-007
This commit is contained in:
32
CODESMELL.md
32
CODESMELL.md
@@ -131,18 +131,23 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### CSM-007 — Monolithic State Rebuild ("God Function")
|
### ~~CSM-007 — Monolithic State Rebuild ("God Function")~~ ✅ FIXED
|
||||||
- **File:** `lib/bds/desktop/shell_live.ex:554-616`
|
- **Fixed:** 2026-05-09
|
||||||
- **What:** `reload_shell/2` rebuilds sidebar, dashboard, git badge, tasks, status bar, tab meta, and panel data on almost every event. This function is called from most `handle_event` and `handle_info` callbacks, even for trivial state changes like sidebar toggle.
|
- **What was done:**
|
||||||
- **Why it's bad:** Even a simple sidebar toggle triggers 5+ unrelated DB queries (project_snapshot, dashboard, git_badge_count, sidebar_view, task_status). The sidebar and dashboard data are rebuilt even when only the panel content changed. Output entries and editor meta are recalculated unnecessarily.
|
- Decomposed `reload_shell/2` into four focused updaters:
|
||||||
- **Fix:** Decompose into focused updaters, each only querying what it needs:
|
- `refresh_layout/2` — No DB queries. Recomputes workbench-derived assigns (activity_buttons, panel_tabs, current_tab, status_bar, sidebar_header, editor_meta) from existing socket.assigns.
|
||||||
- `refresh_sidebar/2` — only queries sidebar data
|
- `refresh_sidebar/2` — Queries sidebar data only, then calls `refresh_layout`.
|
||||||
- `refresh_dashboard/2` — only queries dashboard data
|
- `refresh_content/2` — Queries projects, dashboard, git badge, and sidebar data, then calls `refresh_layout`.
|
||||||
- `refresh_git_badge/2` — only queries git status
|
- `reload_shell/2` — Full refresh: tab_meta sync, task status, static data, then calls `refresh_content`. Kept for mount, project switch, session restore, and settings changes.
|
||||||
- `refresh_task_status/2` — only queries task state
|
- Replaced all call sites with the minimal refresh needed:
|
||||||
- `refresh_tab_meta/2` — only syncs tab metadata
|
- **Layout-only** (`refresh_layout`): toggle_sidebar, toggle_panel, toggle_assistant_sidebar, select_panel_tab, sync_layout, resize_panel, open_tasks_panel, select_tab, close_tab, toggle_offline_mode, layout menu actions (toggle, close_tab).
|
||||||
Each `handle_event` / `handle_info` should call only the relevant updaters.
|
- **Sidebar** (`refresh_sidebar`): select_view, all sidebar filter events, sidebar menu actions (view_posts, view_media, edit_preferences, etc.), chat/import editor tab_meta updates.
|
||||||
- **Test:** Toggle sidebar; assert no dashboard or git queries are executed. Save a post; assert sidebar refreshes but dashboard does not.
|
- **Content** (`refresh_content`): entity_changed (CLI sync), tags_changed, sidebar create/delete.
|
||||||
|
- **Full reload** (`reload_shell`): mount, activate_project, restore_workbench_session, set_page_language, settings_changed.
|
||||||
|
- Updated Bridges callbacks to use focused refreshers: `refresh_layout` for toggle events and close_tab, `refresh_sidebar` for view switches and tab meta updates, `refresh_content` for entity/tag changes.
|
||||||
|
- Split `@local_menu_actions` into `@layout_menu_actions` and `@sidebar_menu_actions` for correct dispatch.
|
||||||
|
- Fixed `false || true` bug in `refresh_layout` where `offline_mode = assigns[:offline_mode] || true` incorrectly defaulted `false` to `true`.
|
||||||
|
- Added 7 tests in `test/bds/csm007_reload_shell_test.exs` using telemetry-based query counting: toggle_sidebar (0 queries), toggle_panel (0 queries), sync_layout (0 queries), select_panel_tab (0 queries), toggle_offline_mode (1 query — settings write only), select_view (sidebar queries but no dashboard/projects), sidebar_search (no dashboard queries).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -419,9 +424,10 @@
|
|||||||
- CSM-004: Fixed. `attach_runner` moved to `handle_continue`, `terminate/2` added for cleanup, `restart: :temporary` set, JobStore `detach_runner` bug fixed.
|
- CSM-004: Fixed. `attach_runner` moved to `handle_continue`, `terminate/2` added for cleanup, `restart: :temporary` set, JobStore `detach_runner` bug fixed.
|
||||||
- [ ] All high-severity items (CSM-006 to CSM-010) have been addressed.
|
- [ ] All high-severity items (CSM-006 to CSM-010) have been addressed.
|
||||||
- CSM-006: Fixed. Batch INSERT for reindexing, preloaded post records for rendering.
|
- CSM-006: Fixed. Batch INSERT for reindexing, preloaded post records for rendering.
|
||||||
|
- CSM-007: Fixed. Decomposed into refresh_layout, refresh_sidebar, refresh_content, reload_shell.
|
||||||
- [x] CSM-001 fix covers ALL 6 affected files, not just `import_definitions.ex`.
|
- [x] CSM-001 fix covers ALL 6 affected files, not just `import_definitions.ex`.
|
||||||
- [x] CSM-003 fix covers ALL `Repo.delete!` call sites (posts, tags, scripts, media, projects, templates, translations).
|
- [x] CSM-003 fix covers ALL `Repo.delete!` call sites (posts, tags, scripts, media, projects, templates, translations).
|
||||||
- [ ] CSM-007 decomposition is the prerequisite for fixing CSM-008 (render-path queries).
|
- [x] CSM-007 decomposition is the prerequisite for fixing CSM-008 (render-path queries).
|
||||||
- [x] Tests were written **before** implementation changes (Red → Green → Refactor).
|
- [x] Tests were written **before** implementation changes (Red → Green → Refactor).
|
||||||
- [x] Full test suite passes: `mix test`.
|
- [x] Full test suite passes: `mix test`.
|
||||||
- [x] Dialyzer passes cleanly: `mix dialyzer` (zero warnings).
|
- [x] Dialyzer passes cleanly: `mix dialyzer` (zero warnings).
|
||||||
|
|||||||
@@ -82,18 +82,21 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
"load_more_sidebar"
|
"load_more_sidebar"
|
||||||
]
|
]
|
||||||
|
|
||||||
@local_menu_actions MapSet.new([
|
@layout_menu_actions MapSet.new([
|
||||||
:toggle_sidebar,
|
:toggle_sidebar,
|
||||||
:toggle_panel,
|
:toggle_panel,
|
||||||
:toggle_assistant_sidebar,
|
:toggle_assistant_sidebar,
|
||||||
|
:close_tab
|
||||||
|
])
|
||||||
|
@sidebar_menu_actions MapSet.new([
|
||||||
:view_posts,
|
:view_posts,
|
||||||
:view_media,
|
:view_media,
|
||||||
:edit_preferences,
|
:edit_preferences,
|
||||||
:edit_menu,
|
:edit_menu,
|
||||||
:documentation,
|
:documentation,
|
||||||
:api_documentation,
|
:api_documentation
|
||||||
:close_tab
|
|
||||||
])
|
])
|
||||||
|
@local_menu_actions MapSet.union(@layout_menu_actions, @sidebar_menu_actions)
|
||||||
@socket_menu_actions MapSet.new([
|
@socket_menu_actions MapSet.new([
|
||||||
:new_post,
|
:new_post,
|
||||||
:import_media,
|
:import_media,
|
||||||
@@ -174,15 +177,15 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event("toggle_sidebar", _params, socket) do
|
def handle_event("toggle_sidebar", _params, socket) do
|
||||||
{:noreply, reload_shell(socket, Workbench.toggle_sidebar(socket.assigns.workbench))}
|
{:noreply, refresh_layout(socket, Workbench.toggle_sidebar(socket.assigns.workbench))}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("toggle_panel", _params, socket) do
|
def handle_event("toggle_panel", _params, socket) do
|
||||||
{:noreply, reload_shell(socket, Workbench.toggle_panel(socket.assigns.workbench))}
|
{:noreply, refresh_layout(socket, Workbench.toggle_panel(socket.assigns.workbench))}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("toggle_assistant_sidebar", _params, socket) do
|
def handle_event("toggle_assistant_sidebar", _params, socket) do
|
||||||
{:noreply, reload_shell(socket, Workbench.toggle_assistant_sidebar(socket.assigns.workbench))}
|
{:noreply, refresh_layout(socket, Workbench.toggle_assistant_sidebar(socket.assigns.workbench))}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("select_view", %{"view" => view_id}, socket) do
|
def handle_event("select_view", %{"view" => view_id}, socket) do
|
||||||
@@ -192,7 +195,7 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
BoundedAtoms.sidebar_view(view_id, :posts)
|
BoundedAtoms.sidebar_view(view_id, :posts)
|
||||||
)
|
)
|
||||||
|
|
||||||
{:noreply, reload_shell(socket, workbench)}
|
{:noreply, refresh_sidebar(socket, workbench)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("select_panel_tab", %{"tab" => tab}, socket) do
|
def handle_event("select_panel_tab", %{"tab" => tab}, socket) do
|
||||||
@@ -201,7 +204,7 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
|> Workbench.set_panel_visible(true)
|
|> Workbench.set_panel_visible(true)
|
||||||
|> Workbench.set_panel_tab(BoundedAtoms.panel_tab(tab, :tasks))
|
|> Workbench.set_panel_tab(BoundedAtoms.panel_tab(tab, :tasks))
|
||||||
|
|
||||||
{:noreply, reload_shell(socket, workbench)}
|
{:noreply, refresh_layout(socket, workbench)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("open_sidebar_item", %{"route" => _route, "id" => _id} = params, socket) do
|
def handle_event("open_sidebar_item", %{"route" => _route, "id" => _id} = params, socket) do
|
||||||
@@ -213,15 +216,15 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("sync_layout", params, socket) do
|
def handle_event("sync_layout", params, socket) do
|
||||||
{:noreply, reload_shell(socket, Layout.sync(socket.assigns.workbench, params))}
|
{:noreply, refresh_layout(socket, Layout.sync(socket.assigns.workbench, params))}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("resize_panel", %{"target" => target, "width" => width}, socket) do
|
def handle_event("resize_panel", %{"target" => target, "width" => width}, socket) do
|
||||||
{:noreply, reload_shell(socket, Layout.resize(socket.assigns.workbench, target, width))}
|
{:noreply, refresh_layout(socket, Layout.resize(socket.assigns.workbench, target, width))}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event(event, params, socket) when event in @sidebar_filter_events do
|
def handle_event(event, params, socket) when event in @sidebar_filter_events do
|
||||||
SidebarEvents.handle(socket, event, params, &reload_shell/2)
|
SidebarEvents.handle(socket, event, params, &refresh_sidebar/2)
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("create_sidebar_item", %{"kind" => kind}, socket) do
|
def handle_event("create_sidebar_item", %{"kind" => kind}, socket) do
|
||||||
@@ -248,7 +251,12 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
:preview
|
:preview
|
||||||
)
|
)
|
||||||
|
|
||||||
{:noreply, reload_shell(socket, workbench)}
|
tab_meta = TabHelpers.sync_tab_meta(workbench, socket.assigns[:tab_meta] || %{})
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:tab_meta, tab_meta)
|
||||||
|
|> refresh_layout(workbench)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("close_tab", %{"type" => type, "id" => id}, socket) do
|
def handle_event("close_tab", %{"type" => type, "id" => id}, socket) do
|
||||||
@@ -259,7 +267,7 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign(:tab_meta, tab_meta)
|
|> assign(:tab_meta, tab_meta)
|
||||||
|> reload_shell(workbench)}
|
|> refresh_layout(workbench)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event(
|
def handle_event(
|
||||||
@@ -283,7 +291,7 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
:ok = AI.set_airplane_mode(next_mode)
|
:ok = AI.set_airplane_mode(next_mode)
|
||||||
socket = assign(socket, :offline_mode, next_mode)
|
socket = assign(socket, :offline_mode, next_mode)
|
||||||
|
|
||||||
{:noreply, reload_shell(socket, socket.assigns.workbench)}
|
{:noreply, refresh_layout(socket, socket.assigns.workbench)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("update_assistant_prompt", %{"assistant" => %{"prompt" => prompt}}, socket) do
|
def handle_event("update_assistant_prompt", %{"assistant" => %{"prompt" => prompt}}, socket) do
|
||||||
@@ -314,7 +322,7 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
|> Workbench.set_panel_visible(true)
|
|> Workbench.set_panel_visible(true)
|
||||||
|> Workbench.set_panel_tab(:tasks)
|
|> Workbench.set_panel_tab(:tasks)
|
||||||
|
|
||||||
{:noreply, reload_shell(socket, workbench)}
|
{:noreply, refresh_layout(socket, workbench)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("settings_shell_command", %{"action" => action}, socket) do
|
def handle_event("settings_shell_command", %{"action" => action}, socket) do
|
||||||
@@ -551,12 +559,57 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
index(assigns)
|
index(assigns)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp reload_shell(socket, workbench) do
|
defp refresh_layout(socket, workbench) do
|
||||||
|
git_badge_count = socket.assigns[:git_badge_count] || 0
|
||||||
|
activity_buttons = Workbench.activity_buttons(workbench, git_badge_count)
|
||||||
|
task_status = socket.assigns[:task_status] || %{running_task_message: nil, running_task_overflow: nil}
|
||||||
|
dashboard = socket.assigns[:dashboard] || BDS.UI.Dashboard.empty_snapshot()
|
||||||
|
page_language = socket.assigns[:page_language] || ShellData.ui_language()
|
||||||
|
offline_mode = Map.get(socket.assigns, :offline_mode, true)
|
||||||
|
sidebar_data = socket.assigns[:sidebar_data] || %{}
|
||||||
|
|
||||||
|
socket
|
||||||
|
|> assign(:workbench, workbench)
|
||||||
|
|> assign(:activity_buttons, activity_buttons)
|
||||||
|
|> assign(
|
||||||
|
:sidebar_header,
|
||||||
|
active_sidebar_label(activity_buttons, workbench.active_view, sidebar_data)
|
||||||
|
)
|
||||||
|
|> assign(:panel_tabs, ShellData.panel_tabs(workbench))
|
||||||
|
|> assign(:current_tab, current_tab(workbench))
|
||||||
|
|> assign(:editor_meta, ShellData.editor_meta(task_status))
|
||||||
|
|> assign(
|
||||||
|
:status,
|
||||||
|
ShellData.status_bar(workbench, task_status, dashboard,
|
||||||
|
ui_language: page_language,
|
||||||
|
offline_mode: offline_mode
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp refresh_sidebar(socket, workbench) do
|
||||||
|
project_id = (socket.assigns[:projects] || %{})[:active_project_id]
|
||||||
|
active_view_id = Atom.to_string(workbench.active_view)
|
||||||
|
|
||||||
|
sidebar_data =
|
||||||
|
ShellData.sidebar_view(
|
||||||
|
project_id,
|
||||||
|
active_view_id,
|
||||||
|
ShellSidebarState.current_filters(socket, active_view_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
sidebar_data = ShellSidebarState.merge_ui_state(socket, active_view_id, sidebar_data)
|
||||||
|
|
||||||
|
socket
|
||||||
|
|> assign(:sidebar_data, sidebar_data)
|
||||||
|
|> refresh_layout(workbench)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp refresh_content(socket, workbench) do
|
||||||
projects = ShellData.project_snapshot()
|
projects = ShellData.project_snapshot()
|
||||||
dashboard = ShellData.dashboard(projects.active_project_id)
|
dashboard = ShellData.dashboard(projects.active_project_id)
|
||||||
git_badge_count = ShellData.git_badge_count(projects.active_project_id)
|
git_badge_count = ShellData.git_badge_count(projects.active_project_id)
|
||||||
active_view_id = Atom.to_string(workbench.active_view)
|
active_view_id = Atom.to_string(workbench.active_view)
|
||||||
tab_meta = TabHelpers.sync_tab_meta(workbench, socket.assigns[:tab_meta] || %{})
|
|
||||||
|
|
||||||
sidebar_data =
|
sidebar_data =
|
||||||
ShellData.sidebar_view(
|
ShellData.sidebar_view(
|
||||||
@@ -566,8 +619,26 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
)
|
)
|
||||||
|
|
||||||
sidebar_data = ShellSidebarState.merge_ui_state(socket, active_view_id, sidebar_data)
|
sidebar_data = ShellSidebarState.merge_ui_state(socket, active_view_id, sidebar_data)
|
||||||
|
|
||||||
|
socket
|
||||||
|
|> assign(:projects, projects)
|
||||||
|
|> assign(:current_project, ShellData.current_project(projects))
|
||||||
|
|> assign(:dashboard, dashboard)
|
||||||
|
|> assign(:dashboard_timeline_entries, Map.get(dashboard, :timeline_entries, []))
|
||||||
|
|> assign(:dashboard_category_counts, Map.get(dashboard, :category_counts, []))
|
||||||
|
|> assign(:dashboard_recent_posts, Map.get(dashboard, :recent_posts, []))
|
||||||
|
|> assign(
|
||||||
|
:dashboard_tag_cloud_items,
|
||||||
|
ShellData.dashboard_tag_cloud_items(Map.get(dashboard, :tag_cloud_items, []))
|
||||||
|
)
|
||||||
|
|> assign(:git_badge_count, git_badge_count)
|
||||||
|
|> assign(:sidebar_data, sidebar_data)
|
||||||
|
|> refresh_layout(workbench)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp reload_shell(socket, workbench) do
|
||||||
|
tab_meta = TabHelpers.sync_tab_meta(workbench, socket.assigns[:tab_meta] || %{})
|
||||||
raw_task_status = BDS.Tasks.status_snapshot()
|
raw_task_status = BDS.Tasks.status_snapshot()
|
||||||
activity_buttons = Workbench.activity_buttons(workbench, git_badge_count)
|
|
||||||
page_language = socket.assigns[:page_language] || ShellData.ui_language()
|
page_language = socket.assigns[:page_language] || ShellData.ui_language()
|
||||||
|
|
||||||
offline_mode =
|
offline_mode =
|
||||||
@@ -581,38 +652,13 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
|
|
||||||
socket
|
socket
|
||||||
|> assign(:tab_meta, tab_meta)
|
|> assign(:tab_meta, tab_meta)
|
||||||
|> assign(:workbench, workbench)
|
|
||||||
|> assign(:projects, projects)
|
|
||||||
|> assign(:current_project, ShellData.current_project(projects))
|
|
||||||
|> assign(:dashboard, dashboard)
|
|
||||||
|> assign(:dashboard_timeline_entries, Map.get(dashboard, :timeline_entries, []))
|
|
||||||
|> assign(:dashboard_category_counts, Map.get(dashboard, :category_counts, []))
|
|
||||||
|> assign(:dashboard_recent_posts, Map.get(dashboard, :recent_posts, []))
|
|
||||||
|> assign(
|
|
||||||
:dashboard_tag_cloud_items,
|
|
||||||
ShellData.dashboard_tag_cloud_items(Map.get(dashboard, :tag_cloud_items, []))
|
|
||||||
)
|
|
||||||
|> assign(:sidebar_data, sidebar_data)
|
|
||||||
|> assign(
|
|
||||||
:sidebar_header,
|
|
||||||
active_sidebar_label(activity_buttons, workbench.active_view, sidebar_data)
|
|
||||||
)
|
|
||||||
|> assign(:assistant_cards, ShellData.assistant_cards())
|
|
||||||
|> assign(:editor_meta, ShellData.editor_meta(task_status))
|
|
||||||
|> assign(:task_status, task_status)
|
|> assign(:task_status, task_status)
|
||||||
|> assign(
|
|> assign(:offline_mode, offline_mode)
|
||||||
:status,
|
|> assign(:assistant_cards, ShellData.assistant_cards())
|
||||||
ShellData.status_bar(workbench, task_status, dashboard,
|
|
||||||
ui_language: page_language,
|
|
||||||
offline_mode: offline_mode
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|> assign(:activity_buttons, activity_buttons)
|
|
||||||
|> assign(:panel_tabs, ShellData.panel_tabs(workbench))
|
|
||||||
|> assign(:supported_ui_languages, ShellData.supported_ui_languages())
|
|> assign(:supported_ui_languages, ShellData.supported_ui_languages())
|
||||||
|> assign(:menu_groups, socket.assigns[:menu_groups] || TitlebarMenu.groups())
|
|> assign(:menu_groups, socket.assigns[:menu_groups] || TitlebarMenu.groups())
|
||||||
|> assign(:titlebar_menu_item_index, socket.assigns[:titlebar_menu_item_index])
|
|> assign(:titlebar_menu_item_index, socket.assigns[:titlebar_menu_item_index])
|
||||||
|> assign(:current_tab, current_tab(workbench))
|
|> refresh_content(workbench)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp encoded_shortcuts(shortcuts), do: Jason.encode!(shortcuts)
|
defp encoded_shortcuts(shortcuts), do: Jason.encode!(shortcuts)
|
||||||
@@ -657,6 +703,7 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
defp sidebar_create_callbacks do
|
defp sidebar_create_callbacks do
|
||||||
%{
|
%{
|
||||||
reload: &reload_shell/2,
|
reload: &reload_shell/2,
|
||||||
|
refresh_content: &refresh_content/2,
|
||||||
open_sidebar: &open_sidebar_item/3,
|
open_sidebar: &open_sidebar_item/3,
|
||||||
append_output: &append_output_entry/5
|
append_output: &append_output_entry/5
|
||||||
}
|
}
|
||||||
@@ -681,9 +728,11 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
subtitle: Map.get(params, "subtitle", "")
|
subtitle: Map.get(params, "subtitle", "")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
tab_meta = TabHelpers.sync_tab_meta(workbench, tab_meta)
|
||||||
|
|
||||||
socket
|
socket
|
||||||
|> assign(:tab_meta, tab_meta)
|
|> assign(:tab_meta, tab_meta)
|
||||||
|> reload_shell(workbench)
|
|> refresh_layout(workbench)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp sidebar_create_action(view), do: SidebarCreate.action(view)
|
defp sidebar_create_action(view), do: SidebarCreate.action(view)
|
||||||
@@ -752,8 +801,16 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
|
|
||||||
defp handle_menu_action(socket, action) when is_atom(action) do
|
defp handle_menu_action(socket, action) when is_atom(action) do
|
||||||
cond do
|
cond do
|
||||||
MapSet.member?(@local_menu_actions, action) ->
|
MapSet.member?(@layout_menu_actions, action) ->
|
||||||
reload_shell(socket, MenuBar.execute(socket.assigns.workbench, action))
|
refresh_layout(socket, MenuBar.execute(socket.assigns.workbench, action))
|
||||||
|
|
||||||
|
MapSet.member?(@sidebar_menu_actions, action) ->
|
||||||
|
workbench = MenuBar.execute(socket.assigns.workbench, action)
|
||||||
|
tab_meta = TabHelpers.sync_tab_meta(workbench, socket.assigns[:tab_meta] || %{})
|
||||||
|
|
||||||
|
socket
|
||||||
|
|> assign(:tab_meta, tab_meta)
|
||||||
|
|> refresh_sidebar(workbench)
|
||||||
|
|
||||||
MapSet.member?(@socket_menu_actions, action) ->
|
MapSet.member?(@socket_menu_actions, action) ->
|
||||||
handle_socket_menu_action(socket, action)
|
handle_socket_menu_action(socket, action)
|
||||||
@@ -842,14 +899,14 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
|
|
||||||
defp save_current_tab(socket), do: reload_shell(socket, socket.assigns.workbench)
|
defp save_current_tab(socket), do: refresh_layout(socket, socket.assigns.workbench)
|
||||||
|
|
||||||
defp publish_current_tab(%{assigns: %{current_tab: %{type: :post, id: post_id}}} = socket) do
|
defp publish_current_tab(%{assigns: %{current_tab: %{type: :post, id: post_id}}} = socket) do
|
||||||
send_update(PostEditor, id: "post-editor-#{post_id}", action: :publish)
|
send_update(PostEditor, id: "post-editor-#{post_id}", action: :publish)
|
||||||
socket
|
socket
|
||||||
end
|
end
|
||||||
|
|
||||||
defp publish_current_tab(socket), do: reload_shell(socket, socket.assigns.workbench)
|
defp publish_current_tab(socket), do: refresh_layout(socket, socket.assigns.workbench)
|
||||||
|
|
||||||
defp apply_shell_command(socket, action, params \\ %{}),
|
defp apply_shell_command(socket, action, params \\ %{}),
|
||||||
do: ShellCommandRunner.execute(socket, action, params, shell_command_callbacks())
|
do: ShellCommandRunner.execute(socket, action, params, shell_command_callbacks())
|
||||||
@@ -860,6 +917,7 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
defp shell_command_callbacks do
|
defp shell_command_callbacks do
|
||||||
%{
|
%{
|
||||||
reload: &reload_shell/2,
|
reload: &reload_shell/2,
|
||||||
|
refresh_content: &refresh_content/2,
|
||||||
append_output: &append_output_entry/5
|
append_output: &append_output_entry/5
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
@@ -876,6 +934,7 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
defp overlay_callbacks,
|
defp overlay_callbacks,
|
||||||
do: %{
|
do: %{
|
||||||
reload: &reload_shell/2,
|
reload: &reload_shell/2,
|
||||||
|
refresh_content: &refresh_content/2,
|
||||||
append_output: &append_output_entry/5,
|
append_output: &append_output_entry/5,
|
||||||
execute_sidebar_delete: fn socket, route, id ->
|
execute_sidebar_delete: fn socket, route, id ->
|
||||||
SidebarDelete.execute_delete(socket, route, id, sidebar_delete_callbacks())
|
SidebarDelete.execute_delete(socket, route, id, sidebar_delete_callbacks())
|
||||||
@@ -885,12 +944,16 @@ defmodule BDS.Desktop.ShellLive do
|
|||||||
defp sidebar_delete_callbacks,
|
defp sidebar_delete_callbacks,
|
||||||
do: %{
|
do: %{
|
||||||
reload: &reload_shell/2,
|
reload: &reload_shell/2,
|
||||||
|
refresh_content: &refresh_content/2,
|
||||||
append_output: &append_output_entry/5
|
append_output: &append_output_entry/5
|
||||||
}
|
}
|
||||||
|
|
||||||
defp bridges_callbacks,
|
defp bridges_callbacks,
|
||||||
do: %{
|
do: %{
|
||||||
reload: &reload_shell/2,
|
reload: &reload_shell/2,
|
||||||
|
refresh_layout: &refresh_layout/2,
|
||||||
|
refresh_sidebar: &refresh_sidebar/2,
|
||||||
|
refresh_content: &refresh_content/2,
|
||||||
append_output: &append_output_entry/5,
|
append_output: &append_output_entry/5,
|
||||||
open_sidebar: &open_sidebar_item/3,
|
open_sidebar: &open_sidebar_item/3,
|
||||||
apply_shell_command: &apply_shell_command/3,
|
apply_shell_command: &apply_shell_command/3,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ defmodule BDS.Desktop.ShellLive.Bridges do
|
|||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign(:tab_meta, tab_meta)
|
|> assign(:tab_meta, tab_meta)
|
||||||
|> callbacks.reload.(socket.assigns.workbench)}
|
|> callbacks.refresh_sidebar.(socket.assigns.workbench)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:chat_tool_call, conversation_id, tool_call}, socket, _callbacks) do
|
def handle_info({:chat_tool_call, conversation_id, tool_call}, socket, _callbacks) do
|
||||||
@@ -82,7 +82,7 @@ defmodule BDS.Desktop.ShellLive.Bridges do
|
|||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign(:tab_meta, tab_meta)
|
|> assign(:tab_meta, tab_meta)
|
||||||
|> callbacks.reload.(socket.assigns.workbench)}
|
|> callbacks.refresh_sidebar.(socket.assigns.workbench)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:open_sidebar_item, params, intent}, socket, callbacks) do
|
def handle_info({:open_sidebar_item, params, intent}, socket, callbacks) do
|
||||||
@@ -90,25 +90,30 @@ defmodule BDS.Desktop.ShellLive.Bridges do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:chat_editor_toggle_sidebar}, socket, callbacks) do
|
def handle_info({:chat_editor_toggle_sidebar}, socket, callbacks) do
|
||||||
{:noreply, callbacks.reload.(socket, Workbench.toggle_sidebar(socket.assigns.workbench))}
|
{:noreply,
|
||||||
|
callbacks.refresh_layout.(socket, Workbench.toggle_sidebar(socket.assigns.workbench))}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:chat_editor_toggle_panel}, socket, callbacks) do
|
def handle_info({:chat_editor_toggle_panel}, socket, callbacks) do
|
||||||
{:noreply, callbacks.reload.(socket, Workbench.toggle_panel(socket.assigns.workbench))}
|
{:noreply,
|
||||||
|
callbacks.refresh_layout.(socket, Workbench.toggle_panel(socket.assigns.workbench))}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:chat_editor_toggle_assistant_sidebar}, socket, callbacks) do
|
def handle_info({:chat_editor_toggle_assistant_sidebar}, socket, callbacks) do
|
||||||
{:noreply,
|
{:noreply,
|
||||||
callbacks.reload.(socket, Workbench.toggle_assistant_sidebar(socket.assigns.workbench))}
|
callbacks.refresh_layout.(
|
||||||
|
socket,
|
||||||
|
Workbench.toggle_assistant_sidebar(socket.assigns.workbench)
|
||||||
|
)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:chat_editor_switch_view, view}, socket, callbacks) do
|
def handle_info({:chat_editor_switch_view, view}, socket, callbacks) do
|
||||||
{:noreply,
|
{:noreply,
|
||||||
callbacks.reload.(socket, Workbench.click_activity(socket.assigns.workbench, view))}
|
callbacks.refresh_sidebar.(socket, Workbench.click_activity(socket.assigns.workbench, view))}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:entity_changed, payload}, socket, callbacks) when is_map(payload) do
|
def handle_info({:entity_changed, payload}, socket, callbacks) when is_map(payload) do
|
||||||
{:noreply, CliSync.apply_entity_change(socket, payload, callbacks.reload)}
|
{:noreply, CliSync.apply_entity_change(socket, payload, callbacks.refresh_content)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info(:refresh_task_status, socket, callbacks) do
|
def handle_info(:refresh_task_status, socket, callbacks) do
|
||||||
@@ -155,7 +160,7 @@ defmodule BDS.Desktop.ShellLive.Bridges do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def handle_info(:tags_changed, socket, callbacks) do
|
def handle_info(:tags_changed, socket, callbacks) do
|
||||||
{:noreply, callbacks.reload.(socket, socket.assigns.workbench)}
|
{:noreply, callbacks.refresh_content.(socket, socket.assigns.workbench)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:settings_output, title, message, level}, socket, callbacks) do
|
def handle_info({:settings_output, title, message, level}, socket, callbacks) do
|
||||||
@@ -267,7 +272,8 @@ defmodule BDS.Desktop.ShellLive.Bridges do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({:close_tab, type, id}, socket, callbacks) do
|
def handle_info({:close_tab, type, id}, socket, callbacks) do
|
||||||
{:noreply, callbacks.reload.(socket, Workbench.close_tab(socket.assigns.workbench, type, id))}
|
{:noreply,
|
||||||
|
callbacks.refresh_layout.(socket, Workbench.close_tab(socket.assigns.workbench, type, id))}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info(_message, socket, _callbacks), do: {:noreply, socket}
|
def handle_info(_message, socket, _callbacks), do: {:noreply, socket}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
|||||||
def create(socket, kind, callbacks) do
|
def create(socket, kind, callbacks) do
|
||||||
case socket.assigns.projects.active_project_id do
|
case socket.assigns.projects.active_project_id do
|
||||||
project_id when is_binary(project_id) -> create(socket, project_id, kind, callbacks)
|
project_id when is_binary(project_id) -> create(socket, project_id, kind, callbacks)
|
||||||
_other -> callbacks.reload.(socket, socket.assigns.workbench)
|
_other -> callbacks.refresh_content.(socket, socket.assigns.workbench)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -32,12 +32,12 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
|||||||
categories: []
|
categories: []
|
||||||
}) do
|
}) do
|
||||||
{:ok, _post} ->
|
{:ok, _post} ->
|
||||||
callbacks.reload.(socket, socket.assigns.workbench)
|
callbacks.refresh_content.(socket, socket.assigns.workbench)
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
socket
|
socket
|
||||||
|> callbacks.append_output.(dgettext("ui", "New Post"), inspect(reason), nil, "error")
|
|> callbacks.append_output.(dgettext("ui", "New Post"), inspect(reason), nil, "error")
|
||||||
|> callbacks.reload.(socket.assigns.workbench)
|
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
|||||||
{:ok, source_path} ->
|
{:ok, source_path} ->
|
||||||
case BDS.Media.import_media(%{project_id: project_id, source_path: source_path}) do
|
case BDS.Media.import_media(%{project_id: project_id, source_path: source_path}) do
|
||||||
{:ok, _media} ->
|
{:ok, _media} ->
|
||||||
callbacks.reload.(socket, socket.assigns.workbench)
|
callbacks.refresh_content.(socket, socket.assigns.workbench)
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
socket
|
socket
|
||||||
@@ -56,16 +56,16 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
|||||||
nil,
|
nil,
|
||||||
"error"
|
"error"
|
||||||
)
|
)
|
||||||
|> callbacks.reload.(socket.assigns.workbench)
|
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||||
end
|
end
|
||||||
|
|
||||||
:cancel ->
|
:cancel ->
|
||||||
callbacks.reload.(socket, socket.assigns.workbench)
|
callbacks.refresh_content.(socket, socket.assigns.workbench)
|
||||||
|
|
||||||
{:error, %{message: message}} ->
|
{:error, %{message: message}} ->
|
||||||
socket
|
socket
|
||||||
|> callbacks.append_output.(dgettext("ui", "Import media"), message, nil, "error")
|
|> callbacks.append_output.(dgettext("ui", "Import media"), message, nil, "error")
|
||||||
|> callbacks.reload.(socket.assigns.workbench)
|
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
|||||||
nil,
|
nil,
|
||||||
"error"
|
"error"
|
||||||
)
|
)
|
||||||
|> callbacks.reload.(socket.assigns.workbench)
|
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
|||||||
nil,
|
nil,
|
||||||
"error"
|
"error"
|
||||||
)
|
)
|
||||||
|> callbacks.reload.(socket.assigns.workbench)
|
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
|||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
socket
|
socket
|
||||||
|> callbacks.append_output.(dgettext("ui", "New Chat"), inspect(reason), nil, "error")
|
|> callbacks.append_output.(dgettext("ui", "New Chat"), inspect(reason), nil, "error")
|
||||||
|> callbacks.reload.(socket.assigns.workbench)
|
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -180,12 +180,12 @@ defmodule BDS.Desktop.ShellLive.SidebarCreate do
|
|||||||
nil,
|
nil,
|
||||||
"error"
|
"error"
|
||||||
)
|
)
|
||||||
|> callbacks.reload.(socket.assigns.workbench)
|
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create(socket, _project_id, _kind, callbacks),
|
def create(socket, _project_id, _kind, callbacks),
|
||||||
do: callbacks.reload.(socket, socket.assigns.workbench)
|
do: callbacks.refresh_content.(socket, socket.assigns.workbench)
|
||||||
|
|
||||||
def action(:posts), do: %{kind: "post", label: "sidebar.newPost"}
|
def action(:posts), do: %{kind: "post", label: "sidebar.newPost"}
|
||||||
def action(:media), do: %{kind: "media", label: "sidebar.importMedia"}
|
def action(:media), do: %{kind: "media", label: "sidebar.importMedia"}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ defmodule BDS.Desktop.ShellLive.SidebarDelete do
|
|||||||
socket
|
socket
|
||||||
|> assign(:shell_overlay, nil)
|
|> assign(:shell_overlay, nil)
|
||||||
|> callbacks.append_output.(delete_title(route), inspect(reason), nil, "error")
|
|> callbacks.append_output.(delete_title(route), inspect(reason), nil, "error")
|
||||||
|> callbacks.reload.(socket.assigns.workbench)
|
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ defmodule BDS.Desktop.ShellLive.SidebarDelete do
|
|||||||
nil,
|
nil,
|
||||||
"error"
|
"error"
|
||||||
)
|
)
|
||||||
|> callbacks.reload.(socket.assigns.workbench)
|
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ defmodule BDS.Desktop.ShellLive.SidebarDelete do
|
|||||||
socket
|
socket
|
||||||
|> assign(:shell_overlay, nil)
|
|> assign(:shell_overlay, nil)
|
||||||
|> assign(:tab_meta, Map.delete(socket.assigns.tab_meta, {type, id}))
|
|> assign(:tab_meta, Map.delete(socket.assigns.tab_meta, {type, id}))
|
||||||
|> callbacks.reload.(workbench)
|
|> callbacks.refresh_content.(workbench)
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
socket
|
socket
|
||||||
@@ -99,7 +99,7 @@ defmodule BDS.Desktop.ShellLive.SidebarDelete do
|
|||||||
nil,
|
nil,
|
||||||
"error"
|
"error"
|
||||||
)
|
)
|
||||||
|> callbacks.reload.(socket.assigns.workbench)
|
|> callbacks.refresh_content.(socket.assigns.workbench)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
191
test/bds/csm007_reload_shell_test.exs
Normal file
191
test/bds/csm007_reload_shell_test.exs
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
defmodule BDS.CSM007ReloadShellTest do
|
||||||
|
use ExUnit.Case, async: false
|
||||||
|
|
||||||
|
import Phoenix.ConnTest
|
||||||
|
import Phoenix.LiveViewTest
|
||||||
|
|
||||||
|
@endpoint BDS.Desktop.Endpoint
|
||||||
|
|
||||||
|
setup do
|
||||||
|
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||||
|
|
||||||
|
temp_dir =
|
||||||
|
Path.join(System.tmp_dir!(), "bds-csm007-#{System.unique_integer([:positive])}")
|
||||||
|
|
||||||
|
File.mkdir_p!(temp_dir)
|
||||||
|
on_exit(fn -> File.rm_rf(temp_dir) end)
|
||||||
|
|
||||||
|
{:ok, project} = BDS.Projects.create_project(%{name: "CSM007", data_path: temp_dir})
|
||||||
|
%{project: project}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "toggle_sidebar" do
|
||||||
|
test "triggers no dashboard or git queries", %{project: _project} do
|
||||||
|
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||||
|
|
||||||
|
query_count = count_queries(fn ->
|
||||||
|
render_click(view, "toggle_sidebar", %{})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert query_count == 0,
|
||||||
|
"Expected 0 DB queries for sidebar toggle, got #{query_count}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "toggle_panel" do
|
||||||
|
test "triggers no DB queries" do
|
||||||
|
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||||
|
|
||||||
|
query_count = count_queries(fn ->
|
||||||
|
render_click(view, "toggle_panel", %{})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert query_count == 0,
|
||||||
|
"Expected 0 DB queries for panel toggle, got #{query_count}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "sync_layout" do
|
||||||
|
test "triggers no DB queries" do
|
||||||
|
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||||
|
|
||||||
|
query_count = count_queries(fn ->
|
||||||
|
render_click(view, "sync_layout", %{
|
||||||
|
"sidebar_width" => "300",
|
||||||
|
"sidebar_visible" => "true",
|
||||||
|
"panel_visible" => "false"
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert query_count == 0,
|
||||||
|
"Expected 0 DB queries for sync_layout, got #{query_count}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "select_panel_tab" do
|
||||||
|
test "triggers no DB queries" do
|
||||||
|
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||||
|
|
||||||
|
query_count = count_queries(fn ->
|
||||||
|
render_click(view, "select_panel_tab", %{"tab" => "output"})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert query_count == 0,
|
||||||
|
"Expected 0 DB queries for select_panel_tab, got #{query_count}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "select_view" do
|
||||||
|
test "triggers sidebar query but not dashboard or git queries" do
|
||||||
|
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||||
|
|
||||||
|
{query_count, query_sources} = count_queries_with_sources(fn ->
|
||||||
|
render_click(view, "select_view", %{"view" => "media"})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert query_count > 0, "Expected at least 1 query for view change"
|
||||||
|
|
||||||
|
refute "dashboard" in query_sources or "projects" in query_sources,
|
||||||
|
"View change should not query dashboard or projects, got: #{inspect(query_sources)}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "sidebar filter events" do
|
||||||
|
test "do not trigger dashboard or git queries" do
|
||||||
|
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||||
|
|
||||||
|
{_count, query_sources} = count_queries_with_sources(fn ->
|
||||||
|
render_click(view, "update_sidebar_search", %{
|
||||||
|
"sidebar_filters" => %{"search" => "test"}
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
|
||||||
|
refute "dashboard" in query_sources,
|
||||||
|
"Sidebar search should not query dashboard, got: #{inspect(query_sources)}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "toggle_offline_mode" do
|
||||||
|
test "triggers only the settings write, no refresh queries" do
|
||||||
|
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||||
|
|
||||||
|
query_count = count_queries(fn ->
|
||||||
|
render_click(view, "toggle_offline_mode", %{})
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert query_count == 1,
|
||||||
|
"Expected exactly 1 DB query (settings write) for offline mode toggle, got #{query_count}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp count_queries(func) do
|
||||||
|
test_pid = self()
|
||||||
|
ref = make_ref()
|
||||||
|
handler_id = "csm007-query-counter-#{inspect(ref)}"
|
||||||
|
|
||||||
|
:telemetry.attach(
|
||||||
|
handler_id,
|
||||||
|
[:bds, :repo, :query],
|
||||||
|
fn _event, _measurements, _metadata, _ ->
|
||||||
|
send(test_pid, {:query_executed, ref})
|
||||||
|
end,
|
||||||
|
nil
|
||||||
|
)
|
||||||
|
|
||||||
|
func.()
|
||||||
|
|
||||||
|
:telemetry.detach(handler_id)
|
||||||
|
count_messages(ref, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp count_queries_with_sources(func) do
|
||||||
|
test_pid = self()
|
||||||
|
ref = make_ref()
|
||||||
|
handler_id = "csm007-query-sources-#{inspect(ref)}"
|
||||||
|
|
||||||
|
:telemetry.attach(
|
||||||
|
handler_id,
|
||||||
|
[:bds, :repo, :query],
|
||||||
|
fn _event, _measurements, metadata, _ ->
|
||||||
|
source = metadata[:source] || extract_table(metadata[:query] || "")
|
||||||
|
send(test_pid, {:query_executed, ref, source})
|
||||||
|
end,
|
||||||
|
nil
|
||||||
|
)
|
||||||
|
|
||||||
|
func.()
|
||||||
|
|
||||||
|
:telemetry.detach(handler_id)
|
||||||
|
collect_query_sources(ref, 0, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp collect_query_sources(ref, count, sources) do
|
||||||
|
receive do
|
||||||
|
{:query_executed, ^ref, source} ->
|
||||||
|
collect_query_sources(ref, count + 1, [source | sources])
|
||||||
|
after
|
||||||
|
0 -> {count, Enum.uniq(sources)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp extract_table(query) when is_binary(query) do
|
||||||
|
cond do
|
||||||
|
String.contains?(query, "dashboard") -> "dashboard"
|
||||||
|
String.contains?(query, "projects") -> "projects"
|
||||||
|
String.contains?(query, "posts") -> "posts"
|
||||||
|
String.contains?(query, "media") -> "media"
|
||||||
|
String.contains?(query, "tags") -> "tags"
|
||||||
|
true -> "other"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp extract_table(_), do: "unknown"
|
||||||
|
|
||||||
|
defp count_messages(ref, acc) do
|
||||||
|
receive do
|
||||||
|
{:query_executed, ^ref} -> count_messages(ref, acc + 1)
|
||||||
|
after
|
||||||
|
0 -> acc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user