mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-10-16 00:15:59 +08:00
Allow users to select the notebook's hub (#1744)
This commit is contained in:
parent
dd5e1af23b
commit
e73af96b86
4 changed files with 142 additions and 51 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue