mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-09-10 15:04:25 +08:00
Implement global Environment Variables from Settings page (#1387)
This commit is contained in:
parent
a1c996be1e
commit
0b9f53a122
10 changed files with 566 additions and 6 deletions
|
@ -3,7 +3,16 @@ defmodule Livebook.Settings do
|
|||
|
||||
# Keeps all Livebook settings that are backed by storage.
|
||||
|
||||
import Ecto.Changeset, only: [apply_action: 2]
|
||||
|
||||
alias Livebook.FileSystem
|
||||
alias Livebook.Settings.EnvVar
|
||||
|
||||
defmodule NotFoundError do
|
||||
@moduledoc false
|
||||
|
||||
defexception [:message, plug_status: 404]
|
||||
end
|
||||
|
||||
@typedoc """
|
||||
An id that is used for filesystem's manipulation, either insertion or removal.
|
||||
|
@ -117,4 +126,113 @@ defmodule Livebook.Settings do
|
|||
def set_update_check_enabled(enabled) do
|
||||
storage().insert(:settings, "global", update_check_enabled: enabled)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets a list of environment variables from storage.
|
||||
"""
|
||||
@spec fetch_env_vars() :: list(EnvVar.t())
|
||||
def fetch_env_vars do
|
||||
for fields <- storage().all(:env_vars) do
|
||||
struct!(EnvVar, Map.delete(fields, :id))
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets one environment variable from storage.
|
||||
|
||||
Raises `RuntimeError` if the environment variable does not exist.
|
||||
"""
|
||||
@spec fetch_env_var!(String.t()) :: EnvVar.t()
|
||||
def fetch_env_var!(id) do
|
||||
case storage().fetch(:env_vars, id) do
|
||||
:error ->
|
||||
raise NotFoundError,
|
||||
message: "could not find an environment variable matching #{inspect(id)}"
|
||||
|
||||
{:ok, fields} ->
|
||||
struct!(EnvVar, Map.delete(fields, :id))
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if environment variable already exists.
|
||||
"""
|
||||
@spec env_var_exists?(String.t()) :: boolean()
|
||||
def env_var_exists?(id) do
|
||||
storage().fetch(:env_vars, id) != :error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Persists the given environment variable.
|
||||
|
||||
With success, notifies interested processes about environment variables
|
||||
data change. Otherwise, it will return an error tuple with changeset.
|
||||
"""
|
||||
@spec set_env_var(EnvVar.t(), map()) ::
|
||||
{:ok, EnvVar.t()} | {:error, Ecto.Changeset.t()}
|
||||
def set_env_var(%EnvVar{} = env_var \\ %EnvVar{}, attrs) do
|
||||
changeset = EnvVar.changeset(env_var, attrs)
|
||||
|
||||
with {:ok, env_var} <- apply_action(changeset, :insert) do
|
||||
save_env_var(env_var)
|
||||
end
|
||||
end
|
||||
|
||||
defp save_env_var(env_var) do
|
||||
attributes = env_var |> Map.from_struct() |> Map.to_list()
|
||||
|
||||
with :ok <- storage().insert(:env_vars, env_var.key, attributes),
|
||||
:ok <- broadcast_env_vars_change() do
|
||||
{:ok, env_var}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes an environment variable from given id.
|
||||
|
||||
Also, it notifies interested processes about environment variables data change.
|
||||
"""
|
||||
@spec delete_env_var(String.t()) :: :ok
|
||||
def delete_env_var(id) do
|
||||
storage().delete(:env_vars, id)
|
||||
broadcast_env_vars_change()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking environment variable changes.
|
||||
"""
|
||||
@spec change_env_var(EnvVar.t(), map()) :: Ecto.Changeset.t()
|
||||
def change_env_var(%EnvVar{} = env_var, attrs \\ %{}) do
|
||||
env_var
|
||||
|> EnvVar.changeset(attrs)
|
||||
|> Map.put(:action, :validate)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Subscribes to updates in settings information.
|
||||
|
||||
## Messages
|
||||
|
||||
* `{:env_vars_changed, env_vars}`
|
||||
|
||||
"""
|
||||
@spec subscribe() :: :ok | {:error, term()}
|
||||
def subscribe do
|
||||
Phoenix.PubSub.subscribe(Livebook.PubSub, "settings")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unsubscribes from `subscribe/0`.
|
||||
"""
|
||||
@spec unsubscribe() :: :ok
|
||||
def unsubscribe do
|
||||
Phoenix.PubSub.unsubscribe(Livebook.PubSub, "settings")
|
||||
end
|
||||
|
||||
# Notifies interested processes about environment variables data change.
|
||||
#
|
||||
# Broadcasts `{:env_vars_changed, env_vars}` message under the `"settings"` topic.
|
||||
defp broadcast_env_vars_change do
|
||||
Phoenix.PubSub.broadcast(Livebook.PubSub, "settings", {:env_vars_changed, fetch_env_vars()})
|
||||
end
|
||||
end
|
||||
|
|
23
lib/livebook/settings/env_var.ex
Normal file
23
lib/livebook/settings/env_var.ex
Normal file
|
@ -0,0 +1,23 @@
|
|||
defmodule Livebook.Settings.EnvVar do
|
||||
@moduledoc false
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
key: String.t(),
|
||||
value: String.t()
|
||||
}
|
||||
|
||||
@primary_key {:key, :string, autogenerate: false}
|
||||
embedded_schema do
|
||||
field :value, :string
|
||||
end
|
||||
|
||||
def changeset(env_var, attrs \\ %{}) do
|
||||
env_var
|
||||
|> cast(attrs, [:key, :value])
|
||||
|> update_change(:key, &String.upcase/1)
|
||||
|> validate_format(:key, ~r/^(?!LB_)\w+$/, message: "cannot start with the LB_ prefix")
|
||||
|> validate_required([:key, :value])
|
||||
end
|
||||
end
|
|
@ -19,6 +19,7 @@ defmodule LivebookWeb.LiveHelpers do
|
|||
|> assign_new(:patch, fn -> nil end)
|
||||
|> assign_new(:navigate, fn -> nil end)
|
||||
|> assign_new(:class, fn -> "" end)
|
||||
|> assign_new(:on_close, fn -> %JS{} end)
|
||||
|> assign(:attrs, assigns_to_attributes(assigns, [:id, :show, :patch, :navigate, :class]))
|
||||
|
||||
~H"""
|
||||
|
@ -39,8 +40,8 @@ defmodule LivebookWeb.LiveHelpers do
|
|||
aria-modal="true"
|
||||
tabindex="0"
|
||||
autofocus
|
||||
phx-window-keydown={hide_modal(@id)}
|
||||
phx-click-away={hide_modal(@id)}
|
||||
phx-window-keydown={hide_modal(@on_close, @id)}
|
||||
phx-click-away={hide_modal(@on_close, @id)}
|
||||
phx-key="escape"
|
||||
>
|
||||
<%= if @patch do %>
|
||||
|
@ -53,7 +54,7 @@ defmodule LivebookWeb.LiveHelpers do
|
|||
type="button"
|
||||
class="absolute top-6 right-6 text-gray-400 flex space-x-1 items-center"
|
||||
aria_label="close modal"
|
||||
phx-click={hide_modal(@id)}
|
||||
phx-click={hide_modal(@on_close, @id)}
|
||||
>
|
||||
<span class="text-sm">(esc)</span>
|
||||
<.remix_icon icon="close-line" class="text-2xl" />
|
||||
|
|
|
@ -7,9 +7,15 @@ defmodule LivebookWeb.SettingsLive do
|
|||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
if connected?(socket) do
|
||||
Livebook.Settings.subscribe()
|
||||
end
|
||||
|
||||
{:ok,
|
||||
assign(socket,
|
||||
file_systems: Livebook.Settings.file_systems(),
|
||||
env_vars: Livebook.Settings.fetch_env_vars(),
|
||||
env_var: nil,
|
||||
autosave_path_state: %{
|
||||
file: autosave_dir(),
|
||||
dialog_opened?: false
|
||||
|
@ -110,6 +116,23 @@ defmodule LivebookWeb.SettingsLive do
|
|||
socket={@socket}
|
||||
/>
|
||||
</div>
|
||||
<!-- Environment variables configuration -->
|
||||
<div class="flex flex-col space-y-4">
|
||||
<h2 class="text-xl text-gray-800 font-semibold pb-2 border-b border-gray-200">
|
||||
Environment variables
|
||||
</h2>
|
||||
<p class="mt-4 text-gray-700">
|
||||
Environment variables are used to store global values and secrets.
|
||||
The global environment variables can be used on the entire Livebook
|
||||
application and is accessible only to the current machine.
|
||||
</p>
|
||||
<.live_component
|
||||
module={LivebookWeb.SettingsLive.EnvVarsComponent}
|
||||
id="env-vars"
|
||||
env_vars={@env_vars}
|
||||
return_to={Routes.settings_path(@socket, :page)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- User settings section -->
|
||||
<div class="flex flex-col space-y-10">
|
||||
|
@ -172,9 +195,28 @@ defmodule LivebookWeb.SettingsLive do
|
|||
/>
|
||||
</.modal>
|
||||
<% end %>
|
||||
|
||||
<%= if @live_action in [:add_env_var, :edit_env_var] do %>
|
||||
<.modal
|
||||
id="env-var-modal"
|
||||
show
|
||||
class="w-full max-w-3xl"
|
||||
on_close={JS.push("clear_env_var")}
|
||||
patch={Routes.settings_path(@socket, :page)}
|
||||
>
|
||||
<.live_component
|
||||
module={LivebookWeb.SettingsLive.EnvVarComponent}
|
||||
id="env-var"
|
||||
env_var={@env_var}
|
||||
return_to={Routes.settings_path(@socket, :page)}
|
||||
/>
|
||||
</.modal>
|
||||
<% end %>
|
||||
"""
|
||||
end
|
||||
|
||||
defp autosave_path_select(%{state: %{file: nil}} = assigns), do: ~H""
|
||||
|
||||
defp autosave_path_select(%{state: %{dialog_opened?: true}} = assigns) do
|
||||
~H"""
|
||||
<div class="w-full h-52">
|
||||
|
@ -218,6 +260,11 @@ defmodule LivebookWeb.SettingsLive do
|
|||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(%{"env_var_id" => key}, _url, socket) do
|
||||
env_var = Livebook.Settings.fetch_env_var!(key)
|
||||
{:noreply, assign(socket, env_var: env_var)}
|
||||
end
|
||||
|
||||
def handle_params(%{"file_system_id" => file_system_id}, _url, socket) do
|
||||
{:noreply, assign(socket, file_system_id: file_system_id)}
|
||||
end
|
||||
|
@ -273,6 +320,10 @@ defmodule LivebookWeb.SettingsLive do
|
|||
{:noreply, assign(socket, :update_check_enabled, enabled)}
|
||||
end
|
||||
|
||||
def handle_event("clear_env_var", _, socket) do
|
||||
{:noreply, assign(socket, env_var: nil)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:file_systems_updated, file_systems}, socket) do
|
||||
{:noreply, assign(socket, file_systems: file_systems)}
|
||||
|
@ -286,12 +337,18 @@ defmodule LivebookWeb.SettingsLive do
|
|||
handle_event("set_autosave_path", %{}, socket)
|
||||
end
|
||||
|
||||
def handle_info({:env_vars_changed, env_vars}, socket) do
|
||||
{:noreply, assign(socket, env_vars: env_vars)}
|
||||
end
|
||||
|
||||
def handle_info(_message, socket), do: {:noreply, socket}
|
||||
|
||||
defp autosave_dir() do
|
||||
Livebook.Settings.autosave_path()
|
||||
|> Livebook.FileSystem.Utils.ensure_dir_path()
|
||||
|> Livebook.FileSystem.File.local()
|
||||
if path = Livebook.Settings.autosave_path() do
|
||||
path
|
||||
|> Livebook.FileSystem.Utils.ensure_dir_path()
|
||||
|> Livebook.FileSystem.File.local()
|
||||
end
|
||||
end
|
||||
|
||||
defp default_autosave_dir() do
|
||||
|
|
97
lib/livebook_web/live/settings_live/env_var_component.ex
Normal file
97
lib/livebook_web/live/settings_live/env_var_component.ex
Normal file
|
@ -0,0 +1,97 @@
|
|||
defmodule LivebookWeb.SettingsLive.EnvVarComponent do
|
||||
use LivebookWeb, :live_component
|
||||
|
||||
alias Livebook.Settings
|
||||
alias Livebook.Settings.EnvVar
|
||||
|
||||
@impl true
|
||||
def update(assigns, socket) do
|
||||
{env_var, operation} =
|
||||
if assigns.env_var,
|
||||
do: {assigns.env_var, :edit},
|
||||
else: {%EnvVar{}, :new}
|
||||
|
||||
changeset = Settings.change_env_var(env_var)
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> assign(changeset: changeset, env_var: env_var, operation: operation)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div class="p-6 flex flex-col space-y-5">
|
||||
<h3 class="text-2xl font-semibold text-gray-800">
|
||||
<%= if @operation == :new, do: "Add environment variable", else: "Edit environment variable" %>
|
||||
</h3>
|
||||
<p class="text-gray-700">
|
||||
Configure your application global environment variables.
|
||||
</p>
|
||||
<.form
|
||||
id={"#{@id}-form"}
|
||||
let={f}
|
||||
for={@changeset}
|
||||
phx-target={@myself}
|
||||
phx-submit="save"
|
||||
phx-change="validate"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
>
|
||||
<div class="flex flex-col space-y-4">
|
||||
<.input_wrapper form={f} field={:key} class="flex flex-col space-y-1">
|
||||
<div class="input-label">
|
||||
Key <span class="text-xs text-gray-500">(alphanumeric and underscore)</span>
|
||||
</div>
|
||||
<%= text_input(f, :key, class: "input", autofocus: @operation == :new) %>
|
||||
</.input_wrapper>
|
||||
<.input_wrapper form={f} field={:value} class="flex flex-col space-y-1">
|
||||
<div class="input-label">Value</div>
|
||||
<%= text_input(f, :value, class: "input", autofocus: @operation == :edit) %>
|
||||
</.input_wrapper>
|
||||
|
||||
<div class="flex space-x-2">
|
||||
<%= submit("Save",
|
||||
class: "button-base button-blue",
|
||||
disabled: not @changeset.valid?,
|
||||
phx_disabled_with: "Adding..."
|
||||
) %>
|
||||
<button
|
||||
type="button"
|
||||
phx-click="cancel"
|
||||
phx-target={@myself}
|
||||
class="button-base button-outlined-gray"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</.form>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("validate", %{"env_var" => attrs}, socket) do
|
||||
{:noreply, assign(socket, changeset: Settings.change_env_var(socket.assigns.env_var, attrs))}
|
||||
end
|
||||
|
||||
def handle_event("cancel", _, socket) do
|
||||
{:noreply, push_patch(socket, to: socket.assigns.return_to)}
|
||||
end
|
||||
|
||||
def handle_event("save", %{"env_var" => attrs}, socket) do
|
||||
if socket.assigns.changeset.valid? do
|
||||
case Settings.set_env_var(socket.assigns.env_var, attrs) do
|
||||
{:ok, _} ->
|
||||
{:noreply, push_patch(socket, to: socket.assigns.return_to)}
|
||||
|
||||
{:error, changeset} ->
|
||||
{:noreply, assign(socket, changeset: changeset)}
|
||||
end
|
||||
else
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
end
|
90
lib/livebook_web/live/settings_live/env_vars_component.ex
Normal file
90
lib/livebook_web/live/settings_live/env_vars_component.ex
Normal file
|
@ -0,0 +1,90 @@
|
|||
defmodule LivebookWeb.SettingsLive.EnvVarsComponent do
|
||||
use LivebookWeb, :live_component
|
||||
|
||||
alias Livebook.Settings
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div id={@id} class="flex flex-col space-y-4">
|
||||
<div class="flex flex-col space-y-4">
|
||||
<%= for env_var <- @env_vars do %>
|
||||
<div class="flex items-center justify-between border border-gray-200 rounded-lg p-4">
|
||||
<.env_var_info socket={@socket} env_var={env_var} myself={@myself} />
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<%= live_patch("Add environment variable",
|
||||
to: Routes.settings_path(@socket, :add_env_var),
|
||||
class: "button-base button-blue"
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp env_var_info(assigns) do
|
||||
~H"""
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 w-full">
|
||||
<div class="place-content-start">
|
||||
<.labeled_text label="Key">
|
||||
<%= @env_var.key %>
|
||||
</.labeled_text>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center place-content-end">
|
||||
<.menu id={"env-var-#{@env_var.key}-menu"}>
|
||||
<:toggle>
|
||||
<button class="icon-button" aria-label="open session menu" type="button">
|
||||
<.remix_icon icon="more-2-fill" class="text-xl" />
|
||||
</button>
|
||||
</:toggle>
|
||||
<:content>
|
||||
<button
|
||||
id={"env-var-#{@env_var.key}-edit"}
|
||||
type="button"
|
||||
phx-click={JS.push("edit_env_var", value: %{env_var: @env_var.key})}
|
||||
phx-target={@myself}
|
||||
role="menuitem"
|
||||
class="menu-item text-gray-600"
|
||||
>
|
||||
<.remix_icon icon="file-edit-line" />
|
||||
<span class="font-medium">Edit</span>
|
||||
</button>
|
||||
<button
|
||||
id={"env-var-#{@env_var.key}-delete"}
|
||||
type="button"
|
||||
phx-click={
|
||||
with_confirm(
|
||||
JS.push("delete_env_var", value: %{env_var: @env_var.key}),
|
||||
title: "Delete #{@env_var.key}",
|
||||
description: "Are you sure you want to delete environment variable?",
|
||||
confirm_text: "Delete",
|
||||
confirm_icon: "delete-bin-6-line"
|
||||
)
|
||||
}
|
||||
phx-target={@myself}
|
||||
role="menuitem"
|
||||
class="menu-item text-red-600"
|
||||
>
|
||||
<.remix_icon icon="delete-bin-line" />
|
||||
<span class="font-medium">Delete</span>
|
||||
</button>
|
||||
</:content>
|
||||
</.menu>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("edit_env_var", %{"env_var" => key}, socket) do
|
||||
{:noreply, push_patch(socket, to: Routes.settings_path(socket, :edit_env_var, key))}
|
||||
end
|
||||
|
||||
def handle_event("delete_env_var", %{"env_var" => key}, socket) do
|
||||
Settings.delete_env_var(key)
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
|
@ -51,6 +51,8 @@ defmodule LivebookWeb.Router do
|
|||
|
||||
live "/settings", SettingsLive, :page
|
||||
live "/settings/add-file-system", SettingsLive, :add_file_system
|
||||
live "/settings/env-var/new", SettingsLive, :add_env_var
|
||||
live "/settings/env-var/edit/:env_var_id", SettingsLive, :edit_env_var
|
||||
|
||||
live "/explore", ExploreLive, :page
|
||||
live "/explore/notebooks/:slug", ExploreLive, :notebook
|
||||
|
|
66
test/livebook/settings_test.exs
Normal file
66
test/livebook/settings_test.exs
Normal file
|
@ -0,0 +1,66 @@
|
|||
defmodule Livebook.SettingsTest do
|
||||
use Livebook.DataCase
|
||||
|
||||
alias Livebook.Settings
|
||||
|
||||
test "fetch_env_vars/0 returns a list of persisted environment variables" do
|
||||
env_var = insert_env_var(:env_var)
|
||||
assert env_var in Settings.fetch_env_vars()
|
||||
|
||||
Settings.delete_env_var(env_var.key)
|
||||
refute env_var in Settings.fetch_env_vars()
|
||||
end
|
||||
|
||||
test "fetch_env_var!/1 returns one persisted fly" do
|
||||
assert_raise Settings.NotFoundError,
|
||||
~s/could not find an environment variable matching "123456"/,
|
||||
fn ->
|
||||
Settings.fetch_env_var!("123456")
|
||||
end
|
||||
|
||||
env_var = insert_env_var(:env_var, key: "123456")
|
||||
assert Settings.fetch_env_var!("123456") == env_var
|
||||
|
||||
Settings.delete_env_var("123456")
|
||||
end
|
||||
|
||||
test "env_var_exists?/1" do
|
||||
refute Settings.env_var_exists?("FOO")
|
||||
insert_env_var(:env_var, key: "FOO")
|
||||
assert Settings.env_var_exists?("FOO")
|
||||
|
||||
Settings.delete_env_var("FOO")
|
||||
end
|
||||
|
||||
describe "set_env_var/1" do
|
||||
test "creates an environment variable" do
|
||||
attrs = params_for(:env_var, key: "FOO_BAR_BAZ")
|
||||
assert {:ok, env_var} = Settings.set_env_var(attrs)
|
||||
|
||||
assert attrs.key == env_var.key
|
||||
assert attrs.value == env_var.value
|
||||
|
||||
Settings.delete_env_var(env_var.key)
|
||||
end
|
||||
|
||||
test "updates an environment variable" do
|
||||
env_var = insert_env_var(:env_var)
|
||||
attrs = %{value: "FOO"}
|
||||
assert {:ok, updated_env_var} = Settings.set_env_var(env_var, attrs)
|
||||
|
||||
assert env_var.key == updated_env_var.key
|
||||
assert updated_env_var.value == attrs.value
|
||||
|
||||
Settings.delete_env_var(env_var.key)
|
||||
end
|
||||
|
||||
test "returns changeset error" do
|
||||
attrs = params_for(:env_var, key: nil)
|
||||
assert {:error, changeset} = Settings.set_env_var(attrs)
|
||||
assert "can't be blank" in errors_on(changeset).key
|
||||
|
||||
assert {:error, changeset} = Settings.set_env_var(%{attrs | key: "LB_FOO"})
|
||||
assert "cannot start with the LB_ prefix" in errors_on(changeset).key
|
||||
end
|
||||
end
|
||||
end
|
87
test/livebook_web/live/settings_live_test.exs
Normal file
87
test/livebook_web/live/settings_live_test.exs
Normal file
|
@ -0,0 +1,87 @@
|
|||
defmodule LivebookWeb.SettingsLiveTest do
|
||||
use LivebookWeb.ConnCase, async: true
|
||||
@moduletag :tmp_dir
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
alias Livebook.Settings
|
||||
|
||||
describe "environment variables configuration" do
|
||||
test "list persisted environment variables", %{conn: conn} do
|
||||
insert_env_var(:env_var, key: "MY_ENVIRONMENT_VAR")
|
||||
{:ok, _view, html} = live(conn, Routes.settings_path(conn, :page))
|
||||
|
||||
assert html =~ "MY_ENVIRONMENT_VAR"
|
||||
end
|
||||
|
||||
test "adds an environment variable", %{conn: conn} do
|
||||
attrs = params_for(:env_var, key: "JAKE_PERALTA_ENV_VAR")
|
||||
|
||||
{:ok, view, html} = live(conn, Routes.settings_path(conn, :add_env_var))
|
||||
|
||||
assert html =~ "Add environment variable"
|
||||
refute html =~ attrs.key
|
||||
|
||||
view
|
||||
|> element("#env-var-form")
|
||||
|> render_change(%{"env_var" => attrs})
|
||||
|
||||
refute view
|
||||
|> element("#env-var-form .invalid-feedback")
|
||||
|> has_element?()
|
||||
|
||||
view
|
||||
|> element("#env-var-form")
|
||||
|> render_submit(%{"env_var" => attrs})
|
||||
|
||||
assert_patch(view, Routes.settings_path(conn, :page))
|
||||
|
||||
assert render(view) =~ attrs.key
|
||||
end
|
||||
|
||||
test "updates an environment variable", %{conn: conn} do
|
||||
env_var = insert_env_var(:env_var, key: "UPDATE_ME")
|
||||
|
||||
{:ok, view, html} = live(conn, Routes.settings_path(conn, :page))
|
||||
|
||||
assert html =~ env_var.key
|
||||
|
||||
view
|
||||
|> with_target("#env-vars")
|
||||
|> render_click("edit_env_var", %{"env_var" => env_var.key})
|
||||
|
||||
assert_patch(view, Routes.settings_path(conn, :edit_env_var, env_var.key))
|
||||
assert render(view) =~ "Edit environment variable"
|
||||
|
||||
form = element(view, "#env-var-form")
|
||||
assert render(form) =~ env_var.value
|
||||
|
||||
render_change(form, %{"env_var" => %{"value" => "123456"}})
|
||||
|
||||
refute view
|
||||
|> element(".invalid-feedback")
|
||||
|> has_element?()
|
||||
|
||||
render_submit(form, %{"env_var" => %{"value" => "123456"}})
|
||||
assert_patch(view, Routes.settings_path(conn, :page))
|
||||
|
||||
updated_env_var = Settings.fetch_env_var!(env_var.key)
|
||||
|
||||
assert updated_env_var.key == env_var.key
|
||||
refute updated_env_var.value == env_var.value
|
||||
end
|
||||
|
||||
test "deletes an environment variable", %{conn: conn} do
|
||||
env_var = insert_env_var(:env_var)
|
||||
{:ok, view, html} = live(conn, Routes.settings_path(conn, :page))
|
||||
|
||||
assert html =~ env_var.key
|
||||
|
||||
view
|
||||
|> with_target("#env-vars")
|
||||
|> render_click("delete_env_var", %{"env_var" => env_var.key})
|
||||
|
||||
refute render(view) =~ env_var.key
|
||||
end
|
||||
end
|
||||
end
|
|
@ -31,13 +31,32 @@ defmodule Livebook.Factory do
|
|||
}
|
||||
end
|
||||
|
||||
def build(:env_var) do
|
||||
%Livebook.Settings.EnvVar{
|
||||
key: "BAR",
|
||||
value: "foo"
|
||||
}
|
||||
end
|
||||
|
||||
def build(factory_name, attrs \\ %{}) do
|
||||
factory_name |> build() |> struct!(attrs)
|
||||
end
|
||||
|
||||
def params_for(factory_name, attrs \\ %{}) do
|
||||
factory_name |> build() |> struct!(attrs) |> Map.from_struct()
|
||||
end
|
||||
|
||||
def insert_hub(factory_name, attrs \\ %{}) do
|
||||
factory_name
|
||||
|> build(attrs)
|
||||
|> Livebook.Hubs.save_hub()
|
||||
end
|
||||
|
||||
def insert_env_var(factory_name, attrs \\ %{}) do
|
||||
env_var = build(factory_name, attrs)
|
||||
attributes = env_var |> Map.from_struct() |> Map.to_list()
|
||||
Livebook.Storage.current().insert(:env_vars, env_var.key, attributes)
|
||||
|
||||
env_var
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue