Allow users to select the notebook's hub (#1744)

This commit is contained in:
Alexandre de Souza 2023-03-02 18:10:38 -03:00 committed by GitHub
parent dd5e1af23b
commit e73af96b86
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 142 additions and 51 deletions

View file

@ -577,6 +577,14 @@ defmodule Livebook.Session do
GenServer.cast(pid, {:unset_secret, self(), secret_name}) GenServer.cast(pid, {:unset_secret, self(), secret_name})
end 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 """ @doc """
Sends save request to the server. Sends save request to the server.
@ -1238,6 +1246,12 @@ defmodule Livebook.Session do
{:noreply, handle_operation(state, operation)} {:noreply, handle_operation(state, operation)}
end 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 @impl true
def handle_info({:DOWN, ref, :process, _, reason}, %{runtime_monitor_ref: ref} = state) do def handle_info({:DOWN, ref, :process, _, reason}, %{runtime_monitor_ref: ref} = state) do
broadcast_error( broadcast_error(
@ -1805,6 +1819,10 @@ defmodule Livebook.Session do
notify_update(state) notify_update(state)
end 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 after_operation(state, _prev_state, _operation), do: state
defp handle_actions(state, actions) do defp handle_actions(state, actions) do

View file

@ -34,10 +34,12 @@ defmodule Livebook.Session.Data do
:secrets, :secrets,
:mode, :mode,
:apps, :apps,
:app_data :app_data,
:hub
] ]
alias Livebook.{Notebook, Delta, Runtime, JSInterop, FileSystem} alias Livebook.{Notebook, Delta, Runtime, JSInterop, FileSystem}
alias Livebook.Hubs.Provider
alias Livebook.Users.User alias Livebook.Users.User
alias Livebook.Notebook.{Cell, Section, AppSettings} alias Livebook.Notebook.{Cell, Section, AppSettings}
alias Livebook.Utils.Graph alias Livebook.Utils.Graph
@ -58,7 +60,8 @@ defmodule Livebook.Session.Data do
secrets: %{(name :: String.t()) => value :: String.t()}, secrets: %{(name :: String.t()) => value :: String.t()},
mode: session_mode(), mode: session_mode(),
apps: list(app()), apps: list(app()),
app_data: nil | app_data() app_data: nil | app_data(),
hub: Provider.t()
} }
@type section_info :: %{ @type section_info :: %{
@ -218,6 +221,7 @@ defmodule Livebook.Session.Data do
| {:delete_app, client_id(), Livebook.Session.id()} | {:delete_app, client_id(), Livebook.Session.id()}
| {:app_unregistered, client_id()} | {:app_unregistered, client_id()}
| {:app_stop, client_id()} | {:app_stop, client_id()}
| {:set_notebook_hub, client_id(), String.t()}
@type action :: @type action ::
:connect_runtime :connect_runtime
@ -273,7 +277,8 @@ defmodule Livebook.Session.Data do
secrets: %{}, secrets: %{},
mode: opts[:mode], mode: opts[:mode],
apps: [], apps: [],
app_data: app_data app_data: app_data,
hub: Livebook.Hubs.fetch_hub!("personal-hub")
} }
data data
@ -907,6 +912,13 @@ defmodule Livebook.Session.Data do
end end
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} defp with_actions(data, actions \\ []), do: {data, actions}
@ -1823,6 +1835,11 @@ defmodule Livebook.Session.Data do
end end
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 defp set_smart_cell_definitions(data_actions, smart_cell_definitions) do
data_actions data_actions
|> set!(smart_cell_definitions: smart_cell_definitions) |> set!(smart_cell_definitions: smart_cell_definitions)

View file

@ -228,11 +228,11 @@ defmodule LivebookWeb.SessionLive do
global_status={@data_view.global_status} global_status={@data_view.global_status}
/> />
<div <div
class="w-full max-w-screen-lg px-4 sm:pl-8 sm:pr-16 md:pl-16 pt-4 sm:py-5 mx-auto" class="relative w-full max-w-screen-lg px-4 sm:pl-8 sm:pr-16 md:pl-16 pt-4 sm:py-5 mx-auto"
data-el-notebook-content data-el-notebook-content
> >
<div <div
class="flex items-center pb-4 mb-2 space-x-4 border-b border-gray-200" class="flex flex-wrap items-center pb-4 mb-2 border-b border-gray-200"
data-el-notebook-headline data-el-notebook-headline
data-focusable-id="notebook" data-focusable-id="notebook"
id="notebook" id="notebook"
@ -240,51 +240,81 @@ defmodule LivebookWeb.SessionLive do
data-on-value-change="set_notebook_name" data-on-value-change="set_notebook_name"
data-metadata="notebook" data-metadata="notebook"
> >
<h1 <div class="flex grow">
class="grow p-1 -ml-1 text-3xl font-semibold text-gray-800 border border-transparent rounded-lg whitespace-pre-wrap" <h1
tabindex="0" class="p-1 text-3xl font-semibold text-gray-800 border border-transparent rounded-lg whitespace-pre-wrap"
id="notebook-heading" tabindex="0"
data-el-heading id="notebook-heading"
spellcheck="false" data-el-heading
phx-no-format spellcheck="false"
><%= @data_view.notebook_name %></h1> phx-no-format
<.menu id="session-menu"> ><%= @data_view.notebook_name %></h1>
<:toggle> </div>
<button class="icon-button" aria-label="open notebook menu">
<.remix_icon icon="more-2-fill" class="text-xl" /> <div class="flex justify-between items-center pt-1 w-full md:w-auto md:justify-inherit md:items-baseline">
</button> <div class="md:absolute md:left-0">
</:toggle> <.menu position={:bottom_left} id="notebook-hub-menu">
<.menu_item> <:toggle>
<.link patch={~p"/sessions/#{@session.id}/export/livemd"} role="menuitem"> <span class="tooltip right distant relative" data-tooltip="Select one hub">
<.remix_icon icon="download-2-line" /> <div class="border-2 rounded-full border-gray-200 hover:border-gray-400 focus:border-gray-400 w-[40px] h-[40px] flex items-center justify-center">
<span>Export</span> <span aria-label={@data_view.notebook_hub.hub_name}>
</.link> <%= @data_view.notebook_hub.hub_emoji %>
</.menu_item> </span>
<.menu_item> </div>
<button role="menuitem" phx-click="erase_outputs"> </span>
<.remix_icon icon="eraser-fill" /> </:toggle>
<span>Erase outputs</span> <.menu_item :for={hub <- @saved_hubs}>
</button> <button
</.menu_item> id={"select-hub-#{hub.id}"}
<.menu_item> phx-click={JS.push("select_hub", value: %{id: hub.id})}
<button role="menuitem" phx-click="fork_session"> aria-label={hub.name}
<.remix_icon icon="git-branch-line" /> role="menuitem"
<span>Fork</span> >
</button> <%= hub.emoji %>
</.menu_item> <span class="ml-2"><%= hub.name %></span>
<.menu_item> </button>
<a role="menuitem" href={live_dashboard_process_path(@session.pid)} target="_blank"> </.menu_item>
<.remix_icon icon="dashboard-2-line" /> </.menu>
<span>See on Dashboard</span> </div>
</a>
</.menu_item> <.menu id="session-menu">
<.menu_item variant={:danger}> <:toggle>
<.link navigate={~p"/home/sessions/#{@session.id}/close"} role="menuitem"> <button class="icon-button" aria-label="open notebook menu">
<.remix_icon icon="close-circle-line" /> <.remix_icon icon="more-2-fill" class="text-xl" />
<span>Close</span> </button>
</.link> </:toggle>
</.menu_item> <.menu_item>
</.menu> <.link patch={~p"/sessions/#{@session.id}/export/livemd"} role="menuitem">
<.remix_icon icon="download-2-line" />
<span>Export</span>
</.link>
</.menu_item>
<.menu_item>
<button role="menuitem" phx-click="erase_outputs">
<.remix_icon icon="eraser-fill" />
<span>Erase outputs</span>
</button>
</.menu_item>
<.menu_item>
<button role="menuitem" phx-click="fork_session">
<.remix_icon icon="git-branch-line" />
<span>Fork</span>
</button>
</.menu_item>
<.menu_item>
<a role="menuitem" href={live_dashboard_process_path(@session.pid)} target="_blank">
<.remix_icon icon="dashboard-2-line" />
<span>See on Dashboard</span>
</a>
</.menu_item>
<.menu_item variant={:danger}>
<.link navigate={~p"/home/sessions/#{@session.id}/close"} role="menuitem">
<.remix_icon icon="close-circle-line" />
<span>Close</span>
</.link>
</.menu_item>
</.menu>
</div>
</div> </div>
<div> <div>
<.live_component <.live_component
@ -1158,6 +1188,12 @@ defmodule LivebookWeb.SessionLive do
)} )}
end end
def handle_event("select_hub", %{"id" => id}, socket) do
Session.set_notebook_hub(socket.assigns.session.pid, id)
{:noreply, socket}
end
@impl true @impl true
def handle_info({:operation, operation}, socket) do def handle_info({:operation, operation}, socket) do
{:noreply, handle_operation(socket, operation)} {:noreply, handle_operation(socket, operation)}
@ -1764,7 +1800,8 @@ defmodule LivebookWeb.SessionLive do
secrets: data.secrets, secrets: data.secrets,
apps_status: apps_status(data), apps_status: apps_status(data),
app_settings: data.notebook.app_settings, app_settings: data.notebook.app_settings,
apps: data.apps apps: data.apps,
notebook_hub: data.hub
} }
end end

View file

@ -1448,4 +1448,23 @@ defmodule LivebookWeb.SessionLiveTest do
refute render(view) =~ "/apps/#{slug}" refute render(view) =~ "/apps/#{slug}"
end end
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 end