mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-10-25 04:46:04 +08:00
Allow hub secret toggling (#1675)
This commit is contained in:
parent
605498a369
commit
5f6e7176fc
10 changed files with 515 additions and 252 deletions
|
|
@ -82,7 +82,7 @@ defmodule Livebook.Hubs do
|
|||
attributes = struct |> Map.from_struct() |> Map.to_list()
|
||||
:ok = Storage.insert(@namespace, struct.id, attributes)
|
||||
:ok = connect_hub(struct)
|
||||
:ok = Broadcasts.hubs_metadata_changed()
|
||||
:ok = Broadcasts.hub_changed()
|
||||
|
||||
struct
|
||||
end
|
||||
|
|
@ -90,14 +90,23 @@ defmodule Livebook.Hubs do
|
|||
@doc false
|
||||
def delete_hub(id) do
|
||||
with {:ok, hub} <- get_hub(id) do
|
||||
:ok = Provider.disconnect(hub)
|
||||
:ok = Broadcasts.hub_changed()
|
||||
:ok = Storage.delete(@namespace, id)
|
||||
:ok = Broadcasts.hubs_metadata_changed()
|
||||
:ok = disconnect_hub(hub)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp disconnect_hub(hub) do
|
||||
Task.Supervisor.start_child(Livebook.TaskSupervisor, fn ->
|
||||
Process.sleep(30_000)
|
||||
:ok = Provider.disconnect(hub)
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc false
|
||||
def clean_hubs do
|
||||
for hub <- get_hubs(), do: delete_hub(hub.id)
|
||||
|
|
@ -112,7 +121,7 @@ defmodule Livebook.Hubs do
|
|||
|
||||
Topic `hubs:crud`:
|
||||
|
||||
* `:hubs_metadata_changed`
|
||||
* `:hub_changed`
|
||||
|
||||
Topic `hubs:connection`:
|
||||
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ defmodule Livebook.Hubs.Broadcasts do
|
|||
@doc """
|
||||
Broadcasts when hubs changed under `hubs:crud` topic
|
||||
"""
|
||||
@spec hubs_metadata_changed() :: broadcast()
|
||||
def hubs_metadata_changed do
|
||||
broadcast(@crud_topic, :hubs_metadata_changed)
|
||||
@spec hub_changed() :: broadcast()
|
||||
def hub_changed do
|
||||
broadcast(@crud_topic, :hub_changed)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
|
|||
|
|
@ -24,16 +24,16 @@ defmodule LivebookWeb.SidebarHook do
|
|||
{:halt, put_flash(socket, :info, "Livebook is shutting down. You can close this page.")}
|
||||
end
|
||||
|
||||
@connection_events ~w(hub_connected hub_disconnected hubs_metadata_changed)a
|
||||
@connection_events ~w(hub_connected hub_disconnected hubs_changed)a
|
||||
|
||||
defp handle_info(event, socket) when event in @connection_events do
|
||||
{:halt, assign(socket, saved_hubs: Livebook.Hubs.get_metadatas())}
|
||||
{:cont, assign(socket, saved_hubs: Livebook.Hubs.get_metadatas())}
|
||||
end
|
||||
|
||||
@error_events ~w(hub_connection_failed hub_disconnection_failed)a
|
||||
|
||||
defp handle_info({event, _reason}, socket) when event in @error_events do
|
||||
{:halt, assign(socket, saved_hubs: Livebook.Hubs.get_metadatas())}
|
||||
{:cont, assign(socket, saved_hubs: Livebook.Hubs.get_metadatas())}
|
||||
end
|
||||
|
||||
defp handle_info(_event, socket), do: {:cont, socket}
|
||||
|
|
|
|||
|
|
@ -277,12 +277,14 @@ defmodule LivebookWeb.LiveHelpers do
|
|||
<.switch_checkbox
|
||||
name="likes_cats"
|
||||
label="I very much like cats"
|
||||
tooltip="Cats"
|
||||
checked={@likes_cats} />
|
||||
"""
|
||||
def switch_checkbox(assigns) do
|
||||
assigns =
|
||||
assigns
|
||||
|> assign_new(:label, fn -> nil end)
|
||||
|> assign_new(:tooltip, fn -> nil end)
|
||||
|> assign_new(:disabled, fn -> false end)
|
||||
|> assign_new(:class, fn -> "" end)
|
||||
|> assign(
|
||||
|
|
@ -293,7 +295,7 @@ defmodule LivebookWeb.LiveHelpers do
|
|||
~H"""
|
||||
<div class="flex items-center gap-1 sm:gap-3 justify-between">
|
||||
<%= if @label do %>
|
||||
<span class="text-gray-700"><%= @label %></span>
|
||||
<span class="text-gray-700 tooltip top" data-tooltip={@tooltip}><%= @label %></span>
|
||||
<% end %>
|
||||
<label class={"switch-button #{if(@disabled, do: "switch-button--disabled")}"}>
|
||||
<input type="hidden" value="false" name={@name} />
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ defmodule LivebookWeb.SessionLive do
|
|||
<.secrets_list
|
||||
data_view={@data_view}
|
||||
saved_secrets={@saved_secrets}
|
||||
hubs={@saved_hubs}
|
||||
session={@session}
|
||||
socket={@socket}
|
||||
/>
|
||||
|
|
@ -657,7 +658,12 @@ defmodule LivebookWeb.SessionLive do
|
|||
|
||||
<div class="flex flex-col space-y-4 mt-6">
|
||||
<%= for secret when secret.origin in [:app, :startup] <- @saved_secrets do %>
|
||||
<.secrets_item secret={secret} prefix="app" data_secrets={@data_view.secrets} />
|
||||
<.secrets_item
|
||||
secret={secret}
|
||||
prefix={to_string(secret.origin)}
|
||||
data_secrets={@data_view.secrets}
|
||||
hubs={@hubs}
|
||||
/>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
|
|
@ -677,7 +683,12 @@ defmodule LivebookWeb.SessionLive do
|
|||
|
||||
<div class="flex flex-col space-y-4 mt-6">
|
||||
<%= for %{origin: {:hub, id}} = secret <- @saved_secrets do %>
|
||||
<.secrets_item secret={secret} prefix={"hub-#{id}"} data_secrets={@data_view.secrets} />
|
||||
<.secrets_item
|
||||
secret={secret}
|
||||
prefix={"hub-#{id}"}
|
||||
data_secrets={@data_view.secrets}
|
||||
hubs={@hubs}
|
||||
/>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
@ -705,15 +716,15 @@ defmodule LivebookWeb.SessionLive do
|
|||
>
|
||||
<%= @secret.name %>
|
||||
</span>
|
||||
<%= if @secret.origin in [:app, :startup] do %>
|
||||
<.switch_checkbox
|
||||
name="toggle_secret"
|
||||
checked={is_secret_on_session?(@secret, @data_secrets)}
|
||||
phx-click="toggle_secret"
|
||||
phx-value-secret_name={@secret.name}
|
||||
phx-value-secret_value={@secret.value}
|
||||
/>
|
||||
<% end %>
|
||||
<.switch_checkbox
|
||||
name="toggle_secret"
|
||||
checked={secret_toggled?(@secret, @data_secrets)}
|
||||
label={secret_label(@secret, @hubs)}
|
||||
tooltip={secret_tooltip(@secret, @hubs)}
|
||||
phx-click="toggle_secret"
|
||||
phx-value-secret_name={@secret.name}
|
||||
phx-value-secret_value={@secret.value}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col text-gray-800 hidden" id={"#{@prefix}-secret-#{@secret.name}-detail"}>
|
||||
<div class="flex flex-col">
|
||||
|
|
@ -730,15 +741,15 @@ defmodule LivebookWeb.SessionLive do
|
|||
>
|
||||
<%= @secret.name %>
|
||||
</span>
|
||||
<%= if @secret.origin in [:app, :startup] do %>
|
||||
<.switch_checkbox
|
||||
name="toggle_secret"
|
||||
checked={is_secret_on_session?(@secret, @data_secrets)}
|
||||
phx-click="toggle_secret"
|
||||
phx-value-secret_name={@secret.name}
|
||||
phx-value-secret_value={@secret.value}
|
||||
/>
|
||||
<% end %>
|
||||
<.switch_checkbox
|
||||
name="toggle_secret"
|
||||
checked={secret_toggled?(@secret, @data_secrets)}
|
||||
label={secret_label(@secret, @hubs)}
|
||||
tooltip={secret_tooltip(@secret, @hubs)}
|
||||
phx-click="toggle_secret"
|
||||
phx-value-secret_name={@secret.name}
|
||||
phx-value-secret_value={@secret.value}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-row justify-between items-center my-1">
|
||||
<span class="text-sm font-mono break-all flex-row">
|
||||
|
|
@ -1436,6 +1447,10 @@ defmodule LivebookWeb.SessionLive do
|
|||
|> put_flash(:info, "An existing secret has been updated on your Livebook Enterprise")}
|
||||
end
|
||||
|
||||
def handle_info(:hubs_changed, socket) do
|
||||
{:noreply, assign(socket, saved_secrets: get_saved_secrets())}
|
||||
end
|
||||
|
||||
def handle_info({:error, error}, socket) do
|
||||
message = error |> to_string() |> upcase_first()
|
||||
|
||||
|
|
@ -2273,11 +2288,21 @@ defmodule LivebookWeb.SessionLive do
|
|||
:ok
|
||||
end
|
||||
|
||||
defp is_secret_on_session?(secret, secrets) do
|
||||
Map.has_key?(secrets, secret.name)
|
||||
defp secret_toggled?(secret, secrets) do
|
||||
Map.has_key?(secrets, secret.name) and secrets[secret.name] == secret.value
|
||||
end
|
||||
|
||||
defp get_saved_secrets do
|
||||
Enum.sort(Hubs.get_secrets() ++ Secrets.get_secrets())
|
||||
end
|
||||
|
||||
defp secret_label(%{origin: {:hub, id}}, hubs), do: fetch_hub!(id, hubs).emoji
|
||||
defp secret_label(_, _), do: nil
|
||||
|
||||
defp secret_tooltip(%{origin: {:hub, id}}, hubs), do: fetch_hub!(id, hubs).name
|
||||
defp secret_tooltip(_, _), do: nil
|
||||
|
||||
defp fetch_hub!(id, hubs) do
|
||||
Enum.find(hubs, &(&1.id == id)) || raise "unknown hub id: #{id}"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,22 +10,7 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
|> assign(assigns)
|
||||
|> assign(hubs: Livebook.Hubs.get_hubs([:secrets]))
|
||||
|
||||
prefill_form = prefill_secret_name(socket)
|
||||
|
||||
socket =
|
||||
if socket.assigns[:data] do
|
||||
socket
|
||||
else
|
||||
assign(socket,
|
||||
data: %{"name" => prefill_form, "value" => "", "store" => "session"},
|
||||
errors: [{"value", {"can't be blank", []}}],
|
||||
title: title(socket),
|
||||
grant_access: must_grant_access(socket),
|
||||
has_prefill: prefill_form != ""
|
||||
)
|
||||
end
|
||||
|
||||
{:ok, socket}
|
||||
{:ok, assign(socket, prefill_assigns(socket))}
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
|
@ -35,8 +20,12 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
<h3 class="text-2xl font-semibold text-gray-800">
|
||||
<%= @title %>
|
||||
</h3>
|
||||
<%= if @grant_access do %>
|
||||
<.grant_access_message grant_access={@grant_access} target={@myself} />
|
||||
<%= if @grant_access_name do %>
|
||||
<.grant_access_message
|
||||
secret_name={@grant_access_name}
|
||||
secret_origin={@grant_access_origin}
|
||||
target={@myself}
|
||||
/>
|
||||
<% end %>
|
||||
<div class="flex flex-columns gap-4">
|
||||
<%= if @select_secret_ref do %>
|
||||
|
|
@ -49,7 +38,7 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
<%= for {secret_name, _} <- Enum.sort(@secrets) do %>
|
||||
<.secret_with_badge
|
||||
secret_name={secret_name}
|
||||
origin="session"
|
||||
secret_origin="session"
|
||||
stored="Session"
|
||||
action="select_secret"
|
||||
active={secret_name == @prefill_secret_name}
|
||||
|
|
@ -59,7 +48,8 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
<%= for secret <- @saved_secrets do %>
|
||||
<.secret_with_badge
|
||||
secret_name={secret.name}
|
||||
origin={origin(secret)}
|
||||
secret_store={store(secret)}
|
||||
secret_origin={origin(secret)}
|
||||
stored={stored(secret)}
|
||||
action="select_secret"
|
||||
active={false}
|
||||
|
|
@ -150,6 +140,44 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
"""
|
||||
end
|
||||
|
||||
defp secret_with_badge(%{secret_store: "hub"} = assigns) do
|
||||
~H"""
|
||||
<div
|
||||
role="button"
|
||||
class={[
|
||||
"flex justify-between w-full font-mono text-sm p-2 border-b cursor-pointer",
|
||||
if @active do
|
||||
"bg-blue-100 text-blue-700"
|
||||
else
|
||||
"text-gray-700 hover:bg-gray-100"
|
||||
end
|
||||
]}
|
||||
phx-value-name={@secret_name}
|
||||
phx-value-store="hub"
|
||||
phx-value-hub_id={@secret_origin}
|
||||
phx-target={@target}
|
||||
phx-click={@action}
|
||||
>
|
||||
<%= @secret_name %>
|
||||
<span class={[
|
||||
"inline-flex items-center font-sans rounded-full px-2.5 py-0.5 text-xs font-medium bg-gray-100",
|
||||
if @active do
|
||||
"bg-indigo-100 text-blue-800"
|
||||
else
|
||||
"bg-gray-100 text-gray-800"
|
||||
end
|
||||
]}>
|
||||
<%= if @active do %>
|
||||
<svg class="-ml-0.5 mr-1.5 h-2 w-2 text-blue-400" fill="currentColor" viewBox="0 0 8 8">
|
||||
<circle cx="4" cy="4" r="3" />
|
||||
</svg>
|
||||
<% end %>
|
||||
<%= @stored %>
|
||||
</span>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp secret_with_badge(assigns) do
|
||||
~H"""
|
||||
<div
|
||||
|
|
@ -162,8 +190,8 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
"text-gray-700 hover:bg-gray-100"
|
||||
end
|
||||
]}
|
||||
phx-value-secret_name={@secret_name}
|
||||
phx-value-origin={@origin}
|
||||
phx-value-name={@secret_name}
|
||||
phx-value-store={@secret_store}
|
||||
phx-target={@target}
|
||||
phx-click={@action}
|
||||
>
|
||||
|
|
@ -198,20 +226,42 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
icon="error-warning-fill"
|
||||
class="align-middle text-2xl flex text-gray-100 rounded-lg py-2"
|
||||
/>
|
||||
<span class="ml-2 text-sm font-normal text-gray-100">
|
||||
There is a secret named
|
||||
<span class="font-semibold text-white"><%= @grant_access %></span>
|
||||
in your Livebook app. Allow this session to access it?
|
||||
</span>
|
||||
<%= if @secret_origin in ["app", "startup"] do %>
|
||||
<span class="ml-2 text-sm font-normal text-gray-100">
|
||||
There is a secret named
|
||||
<span class="font-semibold text-white"><%= @secret_name %></span>
|
||||
in your Livebook app. Allow this session to access it?
|
||||
</span>
|
||||
<% else %>
|
||||
<span class="ml-2 text-sm font-normal text-gray-100">
|
||||
There is a secret named
|
||||
<span class="font-semibold text-white"><%= @secret_name %></span>
|
||||
in your Livebook Hub. Allow this session to access it?
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<button
|
||||
class="button-base button-gray"
|
||||
phx-click="grant_access"
|
||||
phx-value-secret_name={@grant_access}
|
||||
phx-target={@target}
|
||||
>
|
||||
Grant access
|
||||
</button>
|
||||
<%= if @secret_origin in ["app", "startup"] do %>
|
||||
<button
|
||||
class="button-base button-gray"
|
||||
phx-click="grant_access"
|
||||
phx-value-name={@secret_name}
|
||||
phx-value-store={@secret_origin}
|
||||
phx-target={@target}
|
||||
>
|
||||
Grant access
|
||||
</button>
|
||||
<% else %>
|
||||
<button
|
||||
class="button-base button-gray"
|
||||
phx-click="grant_access"
|
||||
phx-value-name={@secret_name}
|
||||
phx-value-store="hub"
|
||||
phx-value-hub_id={@secret_origin}
|
||||
phx-target={@target}
|
||||
>
|
||||
Grant access
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -219,6 +269,33 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
"""
|
||||
end
|
||||
|
||||
defp prefill_assigns(socket) do
|
||||
secret_name = socket.assigns[:prefill_secret_name]
|
||||
|
||||
assigns = %{
|
||||
data: %{"name" => secret_name, "value" => "", "store" => "session"},
|
||||
errors: [{"value", {"can't be blank", []}}],
|
||||
title: title(socket),
|
||||
grant_access_name: nil,
|
||||
grant_access_origin: "app",
|
||||
has_prefill: !is_nil(secret_name)
|
||||
}
|
||||
|
||||
case Enum.find(socket.assigns.saved_secrets, &(&1.name == secret_name)) do
|
||||
%Secret{name: name, origin: {:hub, id}} ->
|
||||
%{assigns | grant_access_name: name, grant_access_origin: id}
|
||||
|
||||
%Secret{name: name, origin: origin} ->
|
||||
%{assigns | grant_access_name: name, grant_access_origin: to_string(origin)}
|
||||
|
||||
nil ->
|
||||
assigns
|
||||
end
|
||||
end
|
||||
|
||||
defp store(%{origin: {:hub, _id}}), do: "hub"
|
||||
defp store(%{origin: origin}), do: to_string(origin)
|
||||
|
||||
defp origin(%{origin: {:hub, id}}), do: id
|
||||
defp origin(%{origin: origin}), do: to_string(origin)
|
||||
|
||||
|
|
@ -237,34 +314,11 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
else
|
||||
{:error, %{errors: errors}} ->
|
||||
{:noreply, assign(socket, errors: errors)}
|
||||
|
||||
{:error, socket} ->
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event(
|
||||
"select_secret",
|
||||
%{"secret_name" => secret_name, "origin" => "session"},
|
||||
socket
|
||||
) do
|
||||
{:noreply,
|
||||
socket
|
||||
|> push_patch(to: socket.assigns.return_to)
|
||||
|> push_secret_selected(secret_name)}
|
||||
end
|
||||
|
||||
def handle_event("select_secret", %{"secret_name" => secret_name, "origin" => "app"}, socket) do
|
||||
grant_access(socket.assigns.saved_secrets, secret_name, :app, socket)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> push_patch(to: socket.assigns.return_to)
|
||||
|> push_secret_selected(secret_name)}
|
||||
end
|
||||
|
||||
def handle_event("select_secret", %{"secret_name" => secret_name, "origin" => hub_id}, socket) do
|
||||
grant_access(socket.assigns.saved_secrets, secret_name, {:hub, hub_id}, socket)
|
||||
def handle_event("select_secret", %{"name" => secret_name} = attrs, socket) do
|
||||
grant_access(socket.assigns.saved_secrets, secret_name, build_origin(attrs), socket)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|
|
@ -281,8 +335,8 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_event("grant_access", %{"secret_name" => secret_name}, socket) do
|
||||
grant_access(socket.assigns.saved_secrets, secret_name, :app, socket)
|
||||
def handle_event("grant_access", %{"name" => secret_name} = attrs, socket) do
|
||||
grant_access(socket.assigns.saved_secrets, secret_name, build_origin(attrs), socket)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|
|
@ -296,28 +350,12 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
push_event(socket, "secret_selected", %{select_secret_ref: ref, secret_name: secret_name})
|
||||
end
|
||||
|
||||
defp prefill_secret_name(socket) do
|
||||
if unavailable_secret?(socket, socket.assigns.prefill_secret_name),
|
||||
do: socket.assigns.prefill_secret_name,
|
||||
else: ""
|
||||
end
|
||||
|
||||
defp unavailable_secret?(_socket, nil), do: false
|
||||
defp unavailable_secret?(_socket, ""), do: false
|
||||
|
||||
defp unavailable_secret?(socket, preselect_name) do
|
||||
not session?(socket, preselect_name) and
|
||||
not app?(socket, preselect_name) and
|
||||
not hub?(socket, preselect_name)
|
||||
end
|
||||
|
||||
defp title(%{assigns: %{select_secret_ref: nil}}), do: "Add secret"
|
||||
defp title(%{assigns: %{select_secret_options: %{"title" => title}}}), do: title
|
||||
defp title(_), do: "Select secret"
|
||||
|
||||
defp build_origin(%{"store" => "session"}), do: :session
|
||||
defp build_origin(%{"store" => "app"}), do: :app
|
||||
defp build_origin(%{"store" => "hub", "hub_id" => id}), do: {:hub, id}
|
||||
defp build_origin(%{"store" => store}), do: String.to_existing_atom(store)
|
||||
|
||||
defp build_attrs(%{"name" => name, "value" => value} = attrs) do
|
||||
%{name: name, value: value, origin: build_origin(attrs)}
|
||||
|
|
@ -332,38 +370,16 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
Livebook.Session.set_secret(socket.assigns.session.pid, secret)
|
||||
end
|
||||
|
||||
defp set_secret(_socket, %Secret{origin: {:hub, id}} = secret) when is_binary(id) do
|
||||
Livebook.Hubs.create_secret(secret)
|
||||
defp set_secret(socket, %Secret{origin: {:hub, id}} = secret) when is_binary(id) do
|
||||
with :ok <- Livebook.Hubs.create_secret(secret) do
|
||||
Livebook.Session.set_secret(socket.assigns.session.pid, secret)
|
||||
end
|
||||
end
|
||||
|
||||
defp grant_access(secrets, secret_name, origin, socket) do
|
||||
secret = Enum.find(secrets, &(&1.name == secret_name and &1.origin == origin))
|
||||
|
||||
if secret,
|
||||
do: set_secret(socket, secret),
|
||||
else: :ok
|
||||
end
|
||||
|
||||
defp must_grant_access(%{assigns: %{prefill_secret_name: secret_name}} = socket) do
|
||||
if not session?(socket, secret_name) and
|
||||
(app?(socket, secret_name) or hub?(socket, secret_name)) do
|
||||
secret_name
|
||||
end
|
||||
end
|
||||
|
||||
defp session?(socket, secret_name) do
|
||||
Enum.any?(socket.assigns.secrets, &(elem(&1, 0) == secret_name))
|
||||
end
|
||||
|
||||
defp app?(socket, secret_name) do
|
||||
Enum.any?(
|
||||
socket.assigns.saved_secrets,
|
||||
&(&1.name == secret_name and &1.origin in [:app, :startup])
|
||||
)
|
||||
end
|
||||
|
||||
defp hub?(socket, secret_name) do
|
||||
Enum.any?(socket.assigns.saved_secrets, &(&1.name == secret_name))
|
||||
if secret, do: Livebook.Session.set_secret(socket.assigns.session.pid, secret)
|
||||
end
|
||||
|
||||
defp hubs_options(hubs, hub_id) do
|
||||
|
|
|
|||
|
|
@ -1,14 +1,21 @@
|
|||
defmodule LivebookWeb.SessionLive.SecretsComponentTest do
|
||||
use Livebook.EnterpriseIntegrationCase, async: true
|
||||
|
||||
import Livebook.SessionHelpers
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
alias Livebook.Session
|
||||
alias Livebook.Sessions
|
||||
|
||||
describe "enterprise" do
|
||||
setup %{url: url, token: token} do
|
||||
id = Livebook.Utils.random_short_id()
|
||||
setup %{test: name} do
|
||||
start_new_instance(name)
|
||||
|
||||
node = EnterpriseServer.get_node(name)
|
||||
url = EnterpriseServer.url(name)
|
||||
token = EnterpriseServer.token(name)
|
||||
|
||||
id = :erpc.call(node, Enterprise.Integration, :fetch_env!, ["ENTERPRISE_ID"])
|
||||
hub_id = "enterprise-#{id}"
|
||||
|
||||
Livebook.Hubs.subscribe([:connection, :secrets])
|
||||
|
|
@ -25,38 +32,18 @@ defmodule LivebookWeb.SessionLive.SecretsComponentTest do
|
|||
{:ok, session} = Sessions.create_session(notebook: Livebook.Notebook.new())
|
||||
|
||||
on_exit(fn ->
|
||||
Livebook.Hubs.delete_hub(hub_id)
|
||||
Session.close(session.pid)
|
||||
stop_new_instance(name)
|
||||
end)
|
||||
|
||||
{:ok, enterprise: enterprise, session: session}
|
||||
{:ok, enterprise: enterprise, session: session, node: node}
|
||||
end
|
||||
|
||||
test "shows the connected hubs dropdown", %{
|
||||
conn: conn,
|
||||
session: session,
|
||||
enterprise: enterprise
|
||||
} do
|
||||
secret = build(:secret, name: "LESS_IMPORTANT_SECRET", value: "123", origin: enterprise.id)
|
||||
{:ok, view, _html} = live(conn, Routes.session_path(conn, :secrets, session.id))
|
||||
|
||||
assert view
|
||||
|> element(~s{form[phx-submit="save"]})
|
||||
|> render_change(%{
|
||||
data: %{
|
||||
name: secret.name,
|
||||
value: secret.value,
|
||||
store: "hub"
|
||||
}
|
||||
}) =~ ~s(<option value="#{enterprise.id}">#{enterprise.hub_name}</option>)
|
||||
end
|
||||
|
||||
test "creates a secret on Enterprise hub", %{
|
||||
conn: conn,
|
||||
session: session,
|
||||
enterprise: enterprise
|
||||
} do
|
||||
test "creates a secret on Enterprise hub",
|
||||
%{conn: conn, session: session, enterprise: enterprise} do
|
||||
id = enterprise.id
|
||||
secret = build(:secret, name: "BIG_IMPORTANT_SECRET", value: "123", origin: id)
|
||||
secret = build(:secret, name: "BIG_IMPORTANT_SECRET", value: "123", origin: {:hub, id})
|
||||
{:ok, view, _html} = live(conn, Routes.session_path(conn, :secrets, session.id))
|
||||
|
||||
attrs = %{
|
||||
|
|
@ -72,8 +59,142 @@ defmodule LivebookWeb.SessionLive.SecretsComponentTest do
|
|||
render_change(form, attrs)
|
||||
render_submit(form, attrs)
|
||||
|
||||
assert_receive {:secret_created, ^secret}
|
||||
assert render(view) =~ "A new secret has been created on your Livebook Enterprise"
|
||||
assert has_element?(view, "#hub-#{enterprise.id}-secret-#{attrs.data.name}-title")
|
||||
|
||||
assert has_element?(
|
||||
view,
|
||||
"#hub-#{enterprise.id}-secret-#{secret.name}-title span",
|
||||
enterprise.hub_emoji
|
||||
)
|
||||
end
|
||||
|
||||
test "toggle a secret from Enterprise hub",
|
||||
%{conn: conn, session: session, enterprise: enterprise, node: node} do
|
||||
secret =
|
||||
build(:secret,
|
||||
name: "POSTGRES_PASSWORD",
|
||||
value: "postgres",
|
||||
origin: {:hub, enterprise.id}
|
||||
)
|
||||
|
||||
{:ok, view, _html} = live(conn, Routes.session_path(conn, :page, session.id))
|
||||
|
||||
:erpc.call(node, Enterprise.Integration, :create_secret, [secret.name, secret.value])
|
||||
assert_receive {:secret_created, ^secret}
|
||||
|
||||
Session.set_secret(session.pid, secret)
|
||||
assert_session_secret(view, session.pid, secret)
|
||||
end
|
||||
|
||||
test "adding a missing secret using 'Add secret' button",
|
||||
%{conn: conn, session: session, enterprise: enterprise} do
|
||||
secret =
|
||||
build(:secret,
|
||||
name: "PGPASS",
|
||||
value: "postgres",
|
||||
origin: {:hub, enterprise.id}
|
||||
)
|
||||
|
||||
# Subscribe and executes the code to trigger
|
||||
# the `System.EnvError` exception and outputs the 'Add secret' button
|
||||
Session.subscribe(session.id)
|
||||
section_id = insert_section(session.pid)
|
||||
code = ~s{System.fetch_env!("LB_#{secret.name}")}
|
||||
cell_id = insert_text_cell(session.pid, section_id, :code, code)
|
||||
|
||||
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||
assert_receive {:operation, {:add_cell_evaluation_response, _, ^cell_id, _, _}}
|
||||
|
||||
# Enters the session to check if the button exists
|
||||
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
||||
expected_url = Routes.session_path(conn, :secrets, session.id, secret_name: secret.name)
|
||||
add_secret_button = element(view, "a[href='#{expected_url}']")
|
||||
assert has_element?(add_secret_button)
|
||||
|
||||
# Clicks the button and fills the form to create a new secret
|
||||
# that prefilled the name with the received from exception.
|
||||
render_click(add_secret_button)
|
||||
secrets_component = with_target(view, "#secrets-modal")
|
||||
form_element = element(secrets_component, "form[phx-submit='save']")
|
||||
assert has_element?(form_element)
|
||||
data = %{value: secret.value, store: "hub", hub_id: enterprise.id}
|
||||
render_submit(form_element, %{data: data})
|
||||
|
||||
# Checks we received the secret created event from Enterprise
|
||||
assert_receive {:secret_created, ^secret}
|
||||
|
||||
# Checks if the secret is persisted
|
||||
assert secret in Livebook.Hubs.get_secrets()
|
||||
|
||||
# Checks if the secret exists and is inside the session,
|
||||
# then executes the code cell again and checks if the
|
||||
# secret value is what we expected.
|
||||
assert_session_secret(view, session.pid, secret)
|
||||
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||
|
||||
assert_receive {:operation,
|
||||
{:add_cell_evaluation_response, _, ^cell_id, {:text, output}, _}}
|
||||
|
||||
assert output == "\e[32m\"#{secret.value}\"\e[0m"
|
||||
end
|
||||
|
||||
test "granting access for missing secret using 'Add secret' button",
|
||||
%{conn: conn, session: session, enterprise: enterprise, node: node} do
|
||||
secret =
|
||||
build(:secret,
|
||||
name: "MYSQL_PASS",
|
||||
value: "admin",
|
||||
origin: {:hub, enterprise.id}
|
||||
)
|
||||
|
||||
# Subscribe and executes the code to trigger
|
||||
# the `System.EnvError` exception and outputs the 'Add secret' button
|
||||
Session.subscribe(session.id)
|
||||
section_id = insert_section(session.pid)
|
||||
code = ~s{System.fetch_env!("LB_#{secret.name}")}
|
||||
cell_id = insert_text_cell(session.pid, section_id, :code, code)
|
||||
|
||||
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||
assert_receive {:operation, {:add_cell_evaluation_response, _, ^cell_id, _, _}}
|
||||
|
||||
# Enters the session to check if the button exists
|
||||
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
||||
expected_url = Routes.session_path(conn, :secrets, session.id, secret_name: secret.name)
|
||||
add_secret_button = element(view, "a[href='#{expected_url}']")
|
||||
assert has_element?(add_secret_button)
|
||||
|
||||
# Persist the secret from the Enterprise
|
||||
:erpc.call(node, Enterprise.Integration, :create_secret, [secret.name, secret.value])
|
||||
|
||||
# Grant we receive the event, even with eventually delay
|
||||
assert_receive {:secret_created, ^secret}, 10_000
|
||||
|
||||
# Checks if the secret is persisted
|
||||
assert secret in Livebook.Hubs.get_secrets()
|
||||
|
||||
# Clicks the button and checks if the 'Grant access' banner
|
||||
# is being shown, so clicks it's button to set the app secret
|
||||
# to the session, allowing the user to fetches the secret.
|
||||
render_click(add_secret_button)
|
||||
secrets_component = with_target(view, "#secrets-modal")
|
||||
|
||||
assert render(secrets_component) =~ "in your Livebook Hub. Allow this session to access it?"
|
||||
|
||||
grant_access_button = element(secrets_component, "button", "Grant access")
|
||||
render_click(grant_access_button)
|
||||
|
||||
# Checks if the secret exists and is inside the session,
|
||||
# then executes the code cell again and checks if the
|
||||
# secret value is what we expected.
|
||||
assert_session_secret(view, session.pid, secret)
|
||||
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||
|
||||
assert_receive {:operation,
|
||||
{:add_cell_evaluation_response, _, ^cell_id, {:text, output}, _}}
|
||||
|
||||
assert output == "\e[32m\"#{secret.value}\"\e[0m"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
defmodule LivebookWeb.SessionLiveTest do
|
||||
use LivebookWeb.ConnCase, async: true
|
||||
|
||||
import Livebook.SessionHelpers
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
alias Livebook.{Sessions, Session, Settings, Runtime, Users, FileSystem}
|
||||
|
|
@ -921,7 +922,7 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|> Plug.Conn.resp(200, "# My notebook")
|
||||
end)
|
||||
|
||||
index_url = url(bypass.port) <> "/index.livemd"
|
||||
index_url = bypass_url(bypass.port) <> "/index.livemd"
|
||||
{:ok, session} = Sessions.create_session(origin: {:url, index_url})
|
||||
|
||||
assert {:error, {:live_redirect, %{to: "/sessions/" <> session_id}}} =
|
||||
|
|
@ -949,7 +950,7 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|> Plug.Conn.resp(200, "# My notebook")
|
||||
end)
|
||||
|
||||
index_url = url(bypass.port) <> "/index.livemd"
|
||||
index_url = bypass_url(bypass.port) <> "/index.livemd"
|
||||
{:ok, session} = Sessions.create_session(origin: {:url, index_url})
|
||||
|
||||
assert {:error, {:live_redirect, %{to: "/sessions/" <> session_id}}} =
|
||||
|
|
@ -969,7 +970,7 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
Plug.Conn.resp(conn, 500, "Error")
|
||||
end)
|
||||
|
||||
index_url = url(bypass.port) <> "/index.livemd"
|
||||
index_url = bypass_url(bypass.port) <> "/index.livemd"
|
||||
|
||||
{:ok, session} = Sessions.create_session(origin: {:url, index_url})
|
||||
|
||||
|
|
@ -1108,7 +1109,7 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
assert app_secret in Livebook.Secrets.get_secrets()
|
||||
end
|
||||
|
||||
test "shows the 'Add secret' button for unavailable secrets", %{conn: conn, session: session} do
|
||||
test "shows the 'Add secret' button for missing secrets", %{conn: conn, session: session} do
|
||||
secret = build(:secret, name: "ANOTHER_GREAT_SECRET", value: "123456", origin: :session)
|
||||
Session.subscribe(session.id)
|
||||
section_id = insert_section(session.pid)
|
||||
|
|
@ -1125,9 +1126,12 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|> has_element?()
|
||||
end
|
||||
|
||||
test "adding an unavailable secret using 'Add secret' button",
|
||||
test "adding a missing secret using 'Add secret' button",
|
||||
%{conn: conn, session: session} do
|
||||
secret = build(:secret, name: "MYUNAVAILABLESECRET", value: "123456", origin: :session)
|
||||
|
||||
# Subscribe and executes the code to trigger
|
||||
# the `System.EnvError` exception and outputs the 'Add secret' button
|
||||
Session.subscribe(session.id)
|
||||
section_id = insert_section(session.pid)
|
||||
code = ~s{System.fetch_env!("LB_#{secret.name}")}
|
||||
|
|
@ -1136,24 +1140,27 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||
assert_receive {:operation, {:add_cell_evaluation_response, _, ^cell_id, _, _}}
|
||||
|
||||
# Enters the session to check if the button exists
|
||||
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
||||
|
||||
expected_url = Routes.session_path(conn, :secrets, session.id, secret_name: secret.name)
|
||||
|
||||
add_secret_button = element(view, "a[href='#{expected_url}']")
|
||||
|
||||
assert has_element?(add_secret_button)
|
||||
render_click(add_secret_button)
|
||||
|
||||
# Clicks the button and fills the form to create a new secret
|
||||
# that prefilled the name with the received from exception.
|
||||
render_click(add_secret_button)
|
||||
secrets_component = with_target(view, "#secrets-modal")
|
||||
form_element = element(secrets_component, "form[phx-submit='save']")
|
||||
|
||||
assert has_element?(form_element)
|
||||
render_submit(form_element, %{data: %{value: secret.value, store: "session"}})
|
||||
|
||||
assert_session_secret(view, session.pid, secret)
|
||||
# Checks if the secret isn't an app secret
|
||||
refute secret in Livebook.Secrets.get_secrets()
|
||||
|
||||
# Checks if the secret exists and is inside the session,
|
||||
# then executes the code cell again and checks if the
|
||||
# secret value is what we expected.
|
||||
assert_session_secret(view, session.pid, secret)
|
||||
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||
|
||||
assert_receive {:operation,
|
||||
|
|
@ -1165,6 +1172,9 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
test "granting access for unavailable secret using 'Add secret' button",
|
||||
%{conn: conn, session: session} do
|
||||
secret = insert_secret(name: "UNAVAILABLESECRET", value: "123456")
|
||||
|
||||
# Subscribe and executes the code to trigger
|
||||
# the `System.EnvError` exception and outputs the 'Add secret' button
|
||||
Session.subscribe(session.id)
|
||||
section_id = insert_section(session.pid)
|
||||
code = ~s{System.fetch_env!("LB_#{secret.name}")}
|
||||
|
|
@ -1173,22 +1183,30 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||
assert_receive {:operation, {:add_cell_evaluation_response, _, ^cell_id, _, _}}
|
||||
|
||||
# Enters the session to check if the button exists
|
||||
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
||||
|
||||
expected_url = Routes.session_path(conn, :secrets, session.id, secret_name: secret.name)
|
||||
|
||||
add_secret_button = element(view, "a[href='#{expected_url}']")
|
||||
|
||||
assert has_element?(add_secret_button)
|
||||
|
||||
# Checks if the secret is persisted
|
||||
assert secret in Livebook.Secrets.get_secrets()
|
||||
|
||||
# Clicks the button and checks if the 'Grant access' banner
|
||||
# is being shown, so clicks it's button to set the app secret
|
||||
# to the session, allowing the user to fetches the secret.
|
||||
render_click(add_secret_button)
|
||||
secrets_component = with_target(view, "#secrets-modal")
|
||||
|
||||
assert render(secrets_component) =~ "in your Livebook app. Allow this session to access it?"
|
||||
|
||||
grant_access_button = element(secrets_component, "button", "Grant access")
|
||||
render_click(grant_access_button)
|
||||
|
||||
# Checks if the secret exists and is inside the session,
|
||||
# then executes the code cell again and checks if the
|
||||
# secret value is what we expected.
|
||||
assert_session_secret(view, session.pid, secret)
|
||||
|
||||
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||
|
||||
assert_receive {:operation,
|
||||
|
|
@ -1203,8 +1221,73 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
|
||||
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
||||
|
||||
assert render(view) =~ secret.name
|
||||
assert render(view) =~ secret.value
|
||||
# Subscribe and executes the code to trigger
|
||||
# the `System.EnvError` exception and outputs the 'Add secret' button
|
||||
Session.subscribe(session.id)
|
||||
section_id = insert_section(session.pid)
|
||||
code = ~s{System.fetch_env!("LB_#{secret.name}")}
|
||||
cell_id = insert_text_cell(session.pid, section_id, :code, code)
|
||||
|
||||
# Sets the secret directly
|
||||
Session.set_secret(session.pid, secret)
|
||||
|
||||
# Checks if the secret exists and is inside the session,
|
||||
# then executes the code cell again and checks if the
|
||||
# secret value is what we expected.
|
||||
assert_session_secret(view, session.pid, secret)
|
||||
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||
|
||||
assert_receive {:operation,
|
||||
{:add_cell_evaluation_response, _, ^cell_id, {:text, output}, _}}
|
||||
|
||||
assert output == "\e[32m\"#{secret.value}\"\e[0m"
|
||||
end
|
||||
|
||||
test "granting access for unavailable startup secret using 'Add secret' button",
|
||||
%{conn: conn, session: session} do
|
||||
secret = build(:secret, name: "MYSTARTUPSECRET", value: "ChonkyCat", origin: :startup)
|
||||
Livebook.Secrets.set_temporary_secrets([secret])
|
||||
|
||||
# Subscribe and executes the code to trigger
|
||||
# the `System.EnvError` exception and outputs the 'Add secret' button
|
||||
Session.subscribe(session.id)
|
||||
section_id = insert_section(session.pid)
|
||||
code = ~s{System.fetch_env!("LB_#{secret.name}")}
|
||||
cell_id = insert_text_cell(session.pid, section_id, :code, code)
|
||||
|
||||
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||
assert_receive {:operation, {:add_cell_evaluation_response, _, ^cell_id, _, _}}
|
||||
|
||||
# Enters the session to check if the button exists
|
||||
{:ok, view, _} = live(conn, "/sessions/#{session.id}")
|
||||
expected_url = Routes.session_path(conn, :secrets, session.id, secret_name: secret.name)
|
||||
add_secret_button = element(view, "a[href='#{expected_url}']")
|
||||
assert has_element?(add_secret_button)
|
||||
|
||||
# Checks if the secret is persisted
|
||||
assert secret in Livebook.Secrets.get_secrets()
|
||||
|
||||
# Clicks the button and checks if the 'Grant access' banner
|
||||
# is being shown, so clicks it's button to set the app secret
|
||||
# to the session, allowing the user to fetches the secret.
|
||||
render_click(add_secret_button)
|
||||
secrets_component = with_target(view, "#secrets-modal")
|
||||
|
||||
assert render(secrets_component) =~ "in your Livebook app. Allow this session to access it?"
|
||||
|
||||
grant_access_button = element(secrets_component, "button", "Grant access")
|
||||
render_click(grant_access_button)
|
||||
|
||||
# Checks if the secret exists and is inside the session,
|
||||
# then executes the code cell again and checks if the
|
||||
# secret value is what we expected.
|
||||
assert_session_secret(view, session.pid, secret)
|
||||
Session.queue_cell_evaluation(session.pid, cell_id)
|
||||
|
||||
assert_receive {:operation,
|
||||
{:add_cell_evaluation_response, _, ^cell_id, {:text, output}, _}}
|
||||
|
||||
assert output == "\e[32m\"#{secret.value}\"\e[0m"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1303,71 +1386,4 @@ defmodule LivebookWeb.SessionLiveTest do
|
|||
assert output == "\e[32m\"#{String.replace(initial_os_path, "\\", "\\\\")}\"\e[0m"
|
||||
end
|
||||
end
|
||||
|
||||
# Helpers
|
||||
|
||||
defp wait_for_session_update(session_pid) do
|
||||
# This call is synchronous, so it gives the session time
|
||||
# for handling the previously sent change messages.
|
||||
Session.get_data(session_pid)
|
||||
:ok
|
||||
end
|
||||
|
||||
# Utils for sending session requests, waiting for the change to be applied
|
||||
# and retrieving new ids if applicable.
|
||||
|
||||
defp insert_section(session_pid) do
|
||||
Session.insert_section(session_pid, 0)
|
||||
%{notebook: %{sections: [section]}} = Session.get_data(session_pid)
|
||||
section.id
|
||||
end
|
||||
|
||||
defp insert_text_cell(session_pid, section_id, type, content \\ "") do
|
||||
Session.insert_cell(session_pid, section_id, 0, type, %{source: content})
|
||||
%{notebook: %{sections: [%{cells: [cell]}]}} = Session.get_data(session_pid)
|
||||
cell.id
|
||||
end
|
||||
|
||||
defp evaluate_setup(session_pid) do
|
||||
Session.queue_cell_evaluation(session_pid, "setup")
|
||||
assert_receive {:operation, {:add_cell_evaluation_response, _, "setup", _, _}}
|
||||
end
|
||||
|
||||
defp insert_cell_with_output(session_pid, section_id, output) do
|
||||
code =
|
||||
quote do
|
||||
send(
|
||||
Process.group_leader(),
|
||||
{:io_request, self(), make_ref(), {:livebook_put_output, unquote(Macro.escape(output))}}
|
||||
)
|
||||
end
|
||||
|> Macro.to_string()
|
||||
|
||||
cell_id = insert_text_cell(session_pid, section_id, :code, code)
|
||||
Session.queue_cell_evaluation(session_pid, cell_id)
|
||||
assert_receive {:operation, {:add_cell_evaluation_response, _, ^cell_id, _, _}}
|
||||
cell_id
|
||||
end
|
||||
|
||||
defp url(port), do: "http://localhost:#{port}"
|
||||
|
||||
defp close_session_by_id(session_id) do
|
||||
{:ok, session} = Sessions.fetch_session(session_id)
|
||||
Session.close(session.pid)
|
||||
end
|
||||
|
||||
defp assert_session_secret(view, session_pid, secret) do
|
||||
selector =
|
||||
case secret do
|
||||
%{name: name, origin: :session} -> "#session-secret-#{name}-title"
|
||||
%{name: name, origin: :app} -> "#app-secret-#{name}-title"
|
||||
%{name: name, origin: {:hub, id}} -> "#hub-#{id}-secret-#{name}-title"
|
||||
end
|
||||
|
||||
assert has_element?(view, selector)
|
||||
secrets = Session.get_data(session_pid).secrets
|
||||
|
||||
assert Map.has_key?(secrets, secret.name)
|
||||
assert secrets[secret.name] == secret.value
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -23,7 +23,10 @@ defmodule Livebook.EnterpriseIntegrationCase do
|
|||
end
|
||||
|
||||
{:ok,
|
||||
url: EnterpriseServer.url(), token: EnterpriseServer.token(), user: EnterpriseServer.user()}
|
||||
url: EnterpriseServer.url(),
|
||||
token: EnterpriseServer.token(),
|
||||
user: EnterpriseServer.user(),
|
||||
node: EnterpriseServer.get_node()}
|
||||
end
|
||||
|
||||
def start_new_instance(name) do
|
||||
|
|
|
|||
71
test/support/session_helpers.ex
Normal file
71
test/support/session_helpers.ex
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
defmodule Livebook.SessionHelpers do
|
||||
@moduledoc false
|
||||
|
||||
alias Livebook.{Session, Sessions}
|
||||
|
||||
import ExUnit.Assertions
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
def wait_for_session_update(session_pid) do
|
||||
# This call is synchronous, so it gives the session time
|
||||
# for handling the previously sent change messages.
|
||||
Session.get_data(session_pid)
|
||||
:ok
|
||||
end
|
||||
|
||||
def evaluate_setup(session_pid) do
|
||||
Session.queue_cell_evaluation(session_pid, "setup")
|
||||
assert_receive {:operation, {:add_cell_evaluation_response, _, "setup", _, _}}
|
||||
end
|
||||
|
||||
def insert_cell_with_output(session_pid, section_id, output) do
|
||||
code =
|
||||
quote do
|
||||
send(
|
||||
Process.group_leader(),
|
||||
{:io_request, self(), make_ref(), {:livebook_put_output, unquote(Macro.escape(output))}}
|
||||
)
|
||||
end
|
||||
|> Macro.to_string()
|
||||
|
||||
cell_id = insert_text_cell(session_pid, section_id, :code, code)
|
||||
Session.queue_cell_evaluation(session_pid, cell_id)
|
||||
assert_receive {:operation, {:add_cell_evaluation_response, _, ^cell_id, _, _}}
|
||||
cell_id
|
||||
end
|
||||
|
||||
def bypass_url(port), do: "http://localhost:#{port}"
|
||||
|
||||
def close_session_by_id(session_id) do
|
||||
{:ok, session} = Sessions.fetch_session(session_id)
|
||||
Session.close(session.pid)
|
||||
end
|
||||
|
||||
def insert_section(session_pid) do
|
||||
Session.insert_section(session_pid, 0)
|
||||
%{notebook: %{sections: [section]}} = Session.get_data(session_pid)
|
||||
section.id
|
||||
end
|
||||
|
||||
def insert_text_cell(session_pid, section_id, type, content \\ " ") do
|
||||
Session.insert_cell(session_pid, section_id, 0, type, %{source: content})
|
||||
%{notebook: %{sections: [%{cells: [cell]}]}} = Session.get_data(session_pid)
|
||||
cell.id
|
||||
end
|
||||
|
||||
def assert_session_secret(view, session_pid, secret) do
|
||||
selector =
|
||||
case secret do
|
||||
%{name: name, origin: :session} -> "#session-secret-#{name}-title"
|
||||
%{name: name, origin: :app} -> "#app-secret-#{name}-title"
|
||||
%{name: name, origin: :startup} -> "#startup-secret-#{name}-title"
|
||||
%{name: name, origin: {:hub, id}} -> "#hub-#{id}-secret-#{name}-title"
|
||||
end
|
||||
|
||||
assert has_element?(view, selector)
|
||||
secrets = Session.get_data(session_pid).secrets
|
||||
|
||||
assert Map.has_key?(secrets, secret.name)
|
||||
assert secrets[secret.name] == secret.value
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue