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})
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

View file

@ -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)

View file

@ -228,11 +228,11 @@ defmodule LivebookWeb.SessionLive do
global_status={@data_view.global_status}
/>
<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
>
<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-focusable-id="notebook"
id="notebook"
@ -240,51 +240,81 @@ defmodule LivebookWeb.SessionLive do
data-on-value-change="set_notebook_name"
data-metadata="notebook"
>
<h1
class="grow p-1 -ml-1 text-3xl font-semibold text-gray-800 border border-transparent rounded-lg whitespace-pre-wrap"
tabindex="0"
id="notebook-heading"
data-el-heading
spellcheck="false"
phx-no-format
><%= @data_view.notebook_name %></h1>
<.menu id="session-menu">
<:toggle>
<button class="icon-button" aria-label="open notebook menu">
<.remix_icon icon="more-2-fill" class="text-xl" />
</button>
</:toggle>
<.menu_item>
<.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 class="flex grow">
<h1
class="p-1 text-3xl font-semibold text-gray-800 border border-transparent rounded-lg whitespace-pre-wrap"
tabindex="0"
id="notebook-heading"
data-el-heading
spellcheck="false"
phx-no-format
><%= @data_view.notebook_name %></h1>
</div>
<div class="flex justify-between items-center pt-1 w-full md:w-auto md:justify-inherit md:items-baseline">
<div class="md:absolute md:left-0">
<.menu position={:bottom_left} id="notebook-hub-menu">
<:toggle>
<span class="tooltip right distant relative" data-tooltip="Select one hub">
<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 aria-label={@data_view.notebook_hub.hub_name}>
<%= @data_view.notebook_hub.hub_emoji %>
</span>
</div>
</span>
</:toggle>
<.menu_item :for={hub <- @saved_hubs}>
<button
id={"select-hub-#{hub.id}"}
phx-click={JS.push("select_hub", value: %{id: hub.id})}
aria-label={hub.name}
role="menuitem"
>
<%= hub.emoji %>
<span class="ml-2"><%= hub.name %></span>
</button>
</.menu_item>
</.menu>
</div>
<.menu id="session-menu">
<:toggle>
<button class="icon-button" aria-label="open notebook menu">
<.remix_icon icon="more-2-fill" class="text-xl" />
</button>
</:toggle>
<.menu_item>
<.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>
<.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

View file

@ -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