From e73af96b86dba3f9e7c4db4529ddc0b9481a9540 Mon Sep 17 00:00:00 2001 From: Alexandre de Souza Date: Thu, 2 Mar 2023 18:10:38 -0300 Subject: [PATCH] Allow users to select the notebook's hub (#1744) --- lib/livebook/session.ex | 18 +++ lib/livebook/session/data.ex | 23 +++- lib/livebook_web/live/session_live.ex | 133 ++++++++++++------- test/livebook_web/live/session_live_test.exs | 19 +++ 4 files changed, 142 insertions(+), 51 deletions(-) diff --git a/lib/livebook/session.ex b/lib/livebook/session.ex index 3ba688390..09f58b118 100644 --- a/lib/livebook/session.ex +++ b/lib/livebook/session.ex @@ -577,6 +577,14 @@ defmodule Livebook.Session do GenServer.cast(pid, {:unset_secret, self(), secret_name}) end + @doc """ + Sends a hub selection to the server. + """ + @spec set_notebook_hub(pid(), String.t()) :: :ok + def set_notebook_hub(pid, id) do + GenServer.cast(pid, {:set_notebook_hub, self(), id}) + end + @doc """ Sends save request to the server. @@ -1238,6 +1246,12 @@ defmodule Livebook.Session do {:noreply, handle_operation(state, operation)} end + def handle_cast({:set_notebook_hub, client_pid, id}, state) do + client_id = client_id(state, client_pid) + operation = {:set_notebook_hub, client_id, id} + {:noreply, handle_operation(state, operation)} + end + @impl true def handle_info({:DOWN, ref, :process, _, reason}, %{runtime_monitor_ref: ref} = state) do broadcast_error( @@ -1805,6 +1819,10 @@ defmodule Livebook.Session do notify_update(state) end + defp after_operation(state, _prev_state, {:set_notebook_hub, _client_id, _id}) do + notify_update(state) + end + defp after_operation(state, _prev_state, _operation), do: state defp handle_actions(state, actions) do diff --git a/lib/livebook/session/data.ex b/lib/livebook/session/data.ex index baff027cd..c526afb10 100644 --- a/lib/livebook/session/data.ex +++ b/lib/livebook/session/data.ex @@ -34,10 +34,12 @@ defmodule Livebook.Session.Data do :secrets, :mode, :apps, - :app_data + :app_data, + :hub ] alias Livebook.{Notebook, Delta, Runtime, JSInterop, FileSystem} + alias Livebook.Hubs.Provider alias Livebook.Users.User alias Livebook.Notebook.{Cell, Section, AppSettings} alias Livebook.Utils.Graph @@ -58,7 +60,8 @@ defmodule Livebook.Session.Data do secrets: %{(name :: String.t()) => value :: String.t()}, mode: session_mode(), apps: list(app()), - app_data: nil | app_data() + app_data: nil | app_data(), + hub: Provider.t() } @type section_info :: %{ @@ -218,6 +221,7 @@ defmodule Livebook.Session.Data do | {:delete_app, client_id(), Livebook.Session.id()} | {:app_unregistered, client_id()} | {:app_stop, client_id()} + | {:set_notebook_hub, client_id(), String.t()} @type action :: :connect_runtime @@ -273,7 +277,8 @@ defmodule Livebook.Session.Data do secrets: %{}, mode: opts[:mode], apps: [], - app_data: app_data + app_data: app_data, + hub: Livebook.Hubs.fetch_hub!("personal-hub") } data @@ -907,6 +912,13 @@ defmodule Livebook.Session.Data do end end + def apply_operation(data, {:set_notebook_hub, _client_id, id}) do + data + |> with_actions() + |> set_notebook_hub(id) + |> wrap_ok() + end + # === defp with_actions(data, actions \\ []), do: {data, actions} @@ -1823,6 +1835,11 @@ defmodule Livebook.Session.Data do end end + defp set_notebook_hub(data_actions, id) do + hub = Livebook.Hubs.fetch_hub!(id) + set!(data_actions, hub: hub) + end + defp set_smart_cell_definitions(data_actions, smart_cell_definitions) do data_actions |> set!(smart_cell_definitions: smart_cell_definitions) diff --git a/lib/livebook_web/live/session_live.ex b/lib/livebook_web/live/session_live.ex index f04145af2..94728d9ff 100644 --- a/lib/livebook_web/live/session_live.ex +++ b/lib/livebook_web/live/session_live.ex @@ -228,11 +228,11 @@ defmodule LivebookWeb.SessionLive do global_status={@data_view.global_status} />
-

<%= @data_view.notebook_name %>

- <.menu id="session-menu"> - <:toggle> - - - <.menu_item> - <.link patch={~p"/sessions/#{@session.id}/export/livemd"} role="menuitem"> - <.remix_icon icon="download-2-line" /> - Export - - - <.menu_item> - - - <.menu_item> - - - <.menu_item> - - <.remix_icon icon="dashboard-2-line" /> - See on Dashboard - - - <.menu_item variant={:danger}> - <.link navigate={~p"/home/sessions/#{@session.id}/close"} role="menuitem"> - <.remix_icon icon="close-circle-line" /> - Close - - - +
+

<%= @data_view.notebook_name %>

+
+ +
+
+ <.menu position={:bottom_left} id="notebook-hub-menu"> + <:toggle> + +
+ + <%= @data_view.notebook_hub.hub_emoji %> + +
+
+ + <.menu_item :for={hub <- @saved_hubs}> + + + +
+ + <.menu id="session-menu"> + <:toggle> + + + <.menu_item> + <.link patch={~p"/sessions/#{@session.id}/export/livemd"} role="menuitem"> + <.remix_icon icon="download-2-line" /> + Export + + + <.menu_item> + + + <.menu_item> + + + <.menu_item> + + <.remix_icon icon="dashboard-2-line" /> + See on Dashboard + + + <.menu_item variant={:danger}> + <.link navigate={~p"/home/sessions/#{@session.id}/close"} role="menuitem"> + <.remix_icon icon="close-circle-line" /> + Close + + + +
<.live_component @@ -1158,6 +1188,12 @@ defmodule LivebookWeb.SessionLive do )} end + def handle_event("select_hub", %{"id" => id}, socket) do + Session.set_notebook_hub(socket.assigns.session.pid, id) + + {:noreply, socket} + end + @impl true def handle_info({:operation, operation}, socket) do {:noreply, handle_operation(socket, operation)} @@ -1764,7 +1800,8 @@ defmodule LivebookWeb.SessionLive do secrets: data.secrets, apps_status: apps_status(data), app_settings: data.notebook.app_settings, - apps: data.apps + apps: data.apps, + notebook_hub: data.hub } end diff --git a/test/livebook_web/live/session_live_test.exs b/test/livebook_web/live/session_live_test.exs index e54fb8150..819d84328 100644 --- a/test/livebook_web/live/session_live_test.exs +++ b/test/livebook_web/live/session_live_test.exs @@ -1448,4 +1448,23 @@ defmodule LivebookWeb.SessionLiveTest do refute render(view) =~ "/apps/#{slug}" end end + + describe "hubs" do + test "selects the notebook hub", %{conn: conn, session: session} do + hub = insert_hub(:fly) + id = hub.id + + Session.subscribe(session.id) + {:ok, view, _} = live(conn, ~p"/sessions/#{session.id}") + + assert %Livebook.Hubs.Personal{id: "personal-hub"} = Session.get_data(session.pid).hub + + view + |> element(~s/#select-hub-#{id}/) + |> render_click() + + assert_receive {:operation, {:set_notebook_hub, _, ^id}} + assert Session.get_data(session.pid).hub == hub + end + end end