Update to Phoenix LV 1.0 (#2607)

This commit is contained in:
Jonatan Kłosko 2024-05-16 18:17:45 +02:00 committed by GitHub
parent 6a45307e4c
commit 602d852b31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 217 additions and 158 deletions

View file

@ -122,7 +122,6 @@ module.exports = {
addVariant("phx-loading", [".phx-loading&", ".phx-loading &"]);
addVariant("phx-connected", [".phx-connected&", ".phx-connected &"]);
addVariant("phx-error", [".phx-error&", ".phx-error &"]);
addVariant("phx-form-error", [":not(.phx-no-feedback).show-errors &"]);
addVariant("phx-click-loading", [
".phx-click-loading&",
".phx-click-loading &",

View file

@ -19,6 +19,8 @@ defprotocol Livebook.Hubs.Provider do
"""
@type notebook_stamp :: map()
@type field_errors :: list({atom(), list(String.t())})
@doc """
Transforms given hub to `Livebook.Hubs.Metadata` struct.
"""
@ -60,7 +62,7 @@ defprotocol Livebook.Hubs.Provider do
"""
@spec create_secret(t(), Secret.t()) ::
:ok
| {:error, Ecto.Changeset.t()}
| {:error, field_errors()}
| {:transport_error, String.t()}
def create_secret(hub, secret)
@ -69,7 +71,7 @@ defprotocol Livebook.Hubs.Provider do
"""
@spec update_secret(t(), Secret.t()) ::
:ok
| {:error, Ecto.Changeset.t()}
| {:error, field_errors()}
| {:transport_error, String.t()}
def update_secret(hub, secret)
@ -120,7 +122,7 @@ defprotocol Livebook.Hubs.Provider do
"""
@spec create_file_system(t(), FileSystem.t()) ::
:ok
| {:error, Ecto.Changeset.t()}
| {:error, field_errors()}
| {:transport_error, String.t()}
def create_file_system(hub, file_system)
@ -129,7 +131,7 @@ defprotocol Livebook.Hubs.Provider do
"""
@spec update_file_system(t(), FileSystem.t()) ::
:ok
| {:error, Ecto.Changeset.t()}
| {:error, field_errors()}
| {:transport_error, String.t()}
def update_file_system(hub, file_system)

View file

@ -212,7 +212,7 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Team do
def create_secret(%Team{} = team, %Secret{} = secret) do
case Requests.create_secret(team, secret) do
{:ok, %{"id" => _}} -> :ok
{:error, %{"errors" => errors}} -> {:error, add_secret_errors(secret, errors)}
{:error, %{"errors" => errors}} -> {:error, parse_secret_errors(errors)}
any -> any
end
end
@ -220,7 +220,7 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Team do
def update_secret(%Team{} = team, %Secret{} = secret) do
case Requests.update_secret(team, secret) do
{:ok, %{"id" => _}} -> :ok
{:error, %{"errors" => errors}} -> {:error, add_secret_errors(secret, errors)}
{:error, %{"errors" => errors}} -> {:error, parse_secret_errors(errors)}
any -> any
end
end
@ -228,7 +228,7 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Team do
def delete_secret(%Team{} = team, %Secret{} = secret) do
case Requests.delete_secret(team, secret) do
{:ok, _} -> :ok
{:error, %{"errors" => errors}} -> {:error, add_secret_errors(secret, errors)}
{:error, %{"errors" => errors}} -> {:error, parse_secret_errors(errors)}
any -> any
end
end
@ -238,7 +238,7 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Team do
def create_file_system(%Team{} = team, file_system) do
case Requests.create_file_system(team, file_system) do
{:ok, %{"id" => _}} -> :ok
{:error, %{"errors" => errors}} -> {:error, add_file_system_errors(file_system, errors)}
{:error, %{"errors" => errors}} -> {:error, parse_file_system_errors(file_system, errors)}
any -> any
end
end
@ -246,7 +246,7 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Team do
def update_file_system(%Team{} = team, file_system) do
case Requests.update_file_system(team, file_system) do
{:ok, %{"id" => _}} -> :ok
{:error, %{"errors" => errors}} -> {:error, add_file_system_errors(file_system, errors)}
{:error, %{"errors" => errors}} -> {:error, parse_file_system_errors(file_system, errors)}
any -> any
end
end
@ -254,7 +254,7 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Team do
def delete_file_system(%Team{} = team, file_system) do
case Requests.delete_file_system(team, file_system) do
{:ok, _} -> :ok
{:error, %{"errors" => errors}} -> {:error, add_file_system_errors(file_system, errors)}
{:error, %{"errors" => errors}} -> {:error, parse_file_system_errors(file_system, errors)}
any -> any
end
end
@ -276,13 +276,13 @@ defimpl Livebook.Hubs.Provider, for: Livebook.Hubs.Team do
end
end
defp add_secret_errors(%Secret{} = secret, errors_map) do
Requests.add_errors(secret, errors_map)
defp parse_secret_errors(errors_map) do
Requests.to_error_list(Secret, errors_map)
end
defp add_file_system_errors(file_system, errors_map) do
defp parse_file_system_errors(%struct{} = file_system, errors_map) do
%{error_field: field} = FileSystem.external_metadata(file_system)
errors_map = Map.new(errors_map, fn {_key, values} -> {field, values} end)
Requests.add_errors(file_system, errors_map)
Requests.to_error_list(struct, errors_map)
end
end

View file

@ -42,19 +42,18 @@ defmodule Livebook.Teams do
defp create_org_request(%Org{} = org, attrs, callback) when is_function(callback, 1) do
changeset = Org.changeset(org, attrs)
with {:ok, %Org{} = org} <- apply_action(changeset, :insert),
{:ok, response} <- callback.(org) do
{:ok, response}
else
{:error, %Ecto.Changeset{} = changeset} ->
{:error, changeset}
with {:ok, %Org{} = org} <- apply_action(changeset, :insert) do
case callback.(org) do
{:ok, response} ->
{:ok, response}
{:error, %{"errors" => errors}} ->
errors = map_teams_field_to_livebook_field(errors, "key_hash", "teams_key")
{:error, add_org_errors(changeset, errors)}
{:error, %{"errors" => errors}} ->
errors = map_teams_field_to_livebook_field(errors, "key_hash", "teams_key")
{:error, changeset |> add_external_errors(errors) |> Map.replace!(:action, :insert)}
any ->
any
any ->
any
end
end
end
@ -152,10 +151,6 @@ defmodule Livebook.Teams do
Plug.Crypto.KeyGenerator.generate(binary_key, "notebook secret", cache: Plug.Crypto.Keys)
end
defp add_org_errors(%Ecto.Changeset{} = changeset, errors_map) do
Requests.add_errors(changeset, Org.__schema__(:fields), errors_map)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking deployment group changes.
"""
@ -167,15 +162,27 @@ defmodule Livebook.Teams do
@doc """
Creates a Deployment Group.
"""
@spec create_deployment_group(Team.t(), DeploymentGroup.t()) ::
{:ok, pos_integer()}
@spec create_deployment_group(Team.t(), map()) ::
{:ok, DeploymentGroup.t()}
| {:error, Ecto.Changeset.t()}
| {:transport_error, String.t()}
def create_deployment_group(%Team{} = team, deployment_group) do
case Requests.create_deployment_group(team, deployment_group) do
{:ok, %{"id" => id}} -> {:ok, id}
{:error, %{"errors" => errors}} -> {:error, Requests.add_errors(deployment_group, errors)}
any -> any
def create_deployment_group(%Team{} = team, attrs) do
changeset = DeploymentGroup.changeset(%DeploymentGroup{}, attrs)
with {:ok, %DeploymentGroup{} = deployment_group} <- apply_action(changeset, :insert) do
case Requests.create_deployment_group(team, deployment_group) do
{:ok, %{"id" => id}} ->
{:ok, %{deployment_group | id: to_string(id)}}
{:error, %{"errors" => errors}} ->
{:error,
changeset
|> add_external_errors(errors)
|> Map.replace!(:action, :insert)}
any ->
any
end
end
end
@ -200,10 +207,10 @@ defmodule Livebook.Teams do
:ok
{:error, %{"errors" => %{"detail" => error}}} ->
{:error, Requests.add_errors(app_deployment, %{"file" => [error]})}
{:error, add_external_errors(app_deployment, %{"file" => [error]})}
{:error, %{"errors" => errors}} ->
{:error, Requests.add_errors(app_deployment, errors)}
{:error, add_external_errors(app_deployment, errors)}
any ->
any
@ -233,4 +240,13 @@ defmodule Livebook.Teams do
map
end
end
defp add_external_errors(%Ecto.Changeset{data: %struct{}} = changeset, errors_map) do
errors = Requests.to_error_list(struct, errors_map)
Livebook.Utils.put_changeset_errors(changeset, errors)
end
defp add_external_errors(struct, errors_map) do
struct |> Ecto.Changeset.change() |> add_external_errors(errors_map)
end
end

View file

@ -212,22 +212,17 @@ defmodule Livebook.Teams.Requests do
end
@doc """
Add requests errors to a `changeset` for the given `fields`.
Normalizes errors map into errors for the given schema.
"""
def add_errors(%Ecto.Changeset{} = changeset, fields, errors_map) do
@spec to_error_list(module(), %{String.t() => list(String.t())}) ::
list({atom(), list(String.t())})
def to_error_list(struct, errors_map) do
fields = struct.__schema__(:fields) |> MapSet.new()
for {key, errors} <- errors_map,
field = String.to_atom(key),
field in fields,
error <- errors,
reduce: changeset,
do: (acc -> Ecto.Changeset.add_error(acc, field, error))
end
@doc """
Add requests errors to a struct.
"""
def add_errors(%struct{} = value, errors_map) do
value |> Ecto.Changeset.change() |> add_errors(struct.__schema__(:fields), errors_map)
do: {field, errors}
end
@doc false

View file

@ -213,6 +213,19 @@ defmodule Livebook.Utils do
end)
end
@doc """
Adds all the given errors to the changeset for the corresponding
fields.
"""
@spec put_changeset_errors(Ecto.Changeset.t(), list({atom(), list(String.t())})) ::
Ecto.Changeset.t()
def put_changeset_errors(changeset, errors) do
for {field, errors} <- errors,
error <- errors,
reduce: changeset,
do: (changeset -> Ecto.Changeset.add_error(changeset, field, error))
end
@doc ~S"""
Validates if the given string forms valid CLI flags.

View file

@ -30,15 +30,23 @@ defmodule LivebookWeb.FormComponents do
name={@name}
id={@id || @name}
value={Phoenix.HTML.Form.normalize_value("text", @value)}
class={[input_classes(), @class]}
class={[input_classes(@errors), @class]}
{@rest}
/>
</.field_wrapper>
"""
end
defp input_classes() do
"w-full px-3 py-2 bg-gray-50 text-sm font-normal border border-gray-200 rounded-lg placeholder-gray-400 text-gray-600 disabled:opacity-70 disabled:cursor-not-allowed phx-form-error:bg-red-50 phx-form-error:border-red-600 phx-form-error:text-red-600 invalid:bg-red-50 invalid:border-red-600 invalid:text-red-600"
defp input_classes(errors) do
[
"w-full px-3 py-2 text-sm font-normal border rounded-lg placeholder-gray-400 disabled:opacity-70 disabled:cursor-not-allowed",
if errors == [] do
"bg-gray-50 border-gray-200 text-gray-600"
else
"bg-red-50 border-red-600 text-red-600"
end,
"invalid:bg-red-50 invalid:border-red-600 invalid:text-red-600"
]
end
@doc """
@ -65,7 +73,12 @@ defmodule LivebookWeb.FormComponents do
<textarea
id={@id || @name}
name={@name}
class={[input_classes(), "resize-none tiny-scrollbar", @monospace && "font-mono", @class]}
class={[
input_classes(@errors),
"resize-none tiny-scrollbar",
@monospace && "font-mono",
@class
]}
{@rest}
><%= Phoenix.HTML.Form.normalize_value("textarea", @value) %></textarea>
</.field_wrapper>
@ -115,7 +128,7 @@ defmodule LivebookWeb.FormComponents do
name={@name}
id={@id || @name}
value={Phoenix.HTML.Form.normalize_value("text", @value)}
class={[input_classes(), "pr-8", @class]}
class={[input_classes(@errors), "pr-8", @class]}
{@rest}
/>
<div class="flex items-center absolute inset-y-0 right-1">
@ -184,7 +197,7 @@ defmodule LivebookWeb.FormComponents do
name={@name}
id={@id || @name}
value={@value}
class={input_classes()}
class={input_classes(@errors)}
spellcheck="false"
maxlength="7"
{@rest}
@ -221,7 +234,7 @@ defmodule LivebookWeb.FormComponents do
assigns = assigns_from_field(assigns)
~H"""
<div phx-feedback-for={@name} class={[@errors != [] && "show-errors"]}>
<div>
<div class="flex items-center gap-1 sm:gap-3 justify-between">
<span :if={@label} class="text-gray-700 flex gap-1 items-center">
<%= @label %>
@ -281,7 +294,7 @@ defmodule LivebookWeb.FormComponents do
assigns = assigns_from_field(assigns)
~H"""
<div phx-feedback-for={@name} class={[@errors != [] && "show-errors"]}>
<div>
<label class="flex items-center gap-2 cursor-pointer">
<input :if={@unchecked_value} type="hidden" value={@unchecked_value} name={@name} />
<input
@ -327,7 +340,7 @@ defmodule LivebookWeb.FormComponents do
assigns = assigns_from_field(assigns)
~H"""
<div phx-feedback-for={@name} class={[@errors != [] && "show-errors"]}>
<div>
<.label :if={@label} for={@id} help={@help}><%= @label %></.label>
<div class="flex gap-4 text-gray-600">
<label :for={{value, description} <- @options} class="flex items-center gap-2 cursor-pointer">
@ -371,7 +384,7 @@ defmodule LivebookWeb.FormComponents do
assigns = assigns_from_field(assigns)
~H"""
<div phx-feedback-for={@name} class={[@errors != [] && "show-errors"]}>
<div>
<.label :if={@label} for={@id} help={@help}><%= @label %></.label>
<div class="flex">
<label
@ -474,7 +487,8 @@ defmodule LivebookWeb.FormComponents do
id={@id}
name={@name}
class={[
"w-full px-3 py-2 pr-7 appearance-none bg-gray-50 text-sm border border-gray-200 rounded-lg placeholder-gray-400 text-gray-600 phx-form-error:border-red-300 disabled:opacity-70 disabled:cursor-not-allowed",
"w-full px-3 py-2 pr-7 appearance-none bg-gray-50 text-sm border rounded-lg placeholder-gray-400 text-gray-600 disabled:opacity-70 disabled:cursor-not-allowed",
if(@errors == [], do: "border-gray-200", else: "border-red-300"),
@class
]}
{@rest}
@ -491,9 +505,11 @@ defmodule LivebookWeb.FormComponents do
end
defp assigns_from_field(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
errors = if Phoenix.Component.used_input?(field), do: field.errors, else: []
assigns
|> assign(field: nil, id: assigns.id || field.id)
|> assign(:errors, Enum.map(field.errors, &translate_error/1))
|> assign(:errors, Enum.map(errors, &translate_error(&1)))
|> assign_new(:name, fn -> field.name end)
|> assign_new(:value, fn -> field.value end)
end
@ -520,7 +536,7 @@ defmodule LivebookWeb.FormComponents do
defp field_wrapper(assigns) do
~H"""
<div phx-feedback-for={@name} class={[@errors != [] && "show-errors"]}>
<div>
<.label :if={@label} for={@id} help={@help}><%= @label %></.label>
<%= render_slot(@inner_block) %>
<.error :for={msg <- @errors}><%= msg %></.error>
@ -551,7 +567,7 @@ defmodule LivebookWeb.FormComponents do
def error(assigns) do
~H"""
<p class="mt-0.5 text-red-600 text-sm hidden phx-form-error:block">
<p class="mt-0.5 text-red-600 text-sm">
<%= render_slot(@inner_block) %>
</p>
"""

View file

@ -24,12 +24,19 @@
<div class="text-gray-50 w-full">
<form method="post" class="flex flex-col w-full">
<input type="hidden" value={Phoenix.Controller.get_csrf_token()} name="_csrf_token" />
<div phx-feedback-for={@auth_mode} class={[@errors != [] && "show-errors"]}>
<div>
<input
:if={@auth_mode == :password}
type="password"
name="password"
class="px-4 py-2 w-full text-gray-300 placeholder-gray-400 border border-gray-500 rounded-lg bg-transparent phx-form-error:border-red-600 phx-form-error:text-red-600 phx-form-error:placeholder-red-600"
class={[
"px-4 py-2 w-full border rounded-lg bg-transparent",
if @errors == [] do
"border-gray-500 text-gray-300 placeholder-gray-400"
else
"border-red-600 text-red-600 placeholder-red-600"
end
]}
placeholder="Password"
autofocus
/>
@ -37,14 +44,18 @@
:if={@auth_mode == :token}
type="text"
name="token"
class="px-4 py-2 w-full text-gray-300 placeholder-gray-400 border border-gray-500 rounded-lg bg-transparent phx-form-error:border-red-600 phx-form-error:text-red-600 phx-form-error:placeholder-red-600"
class={[
"px-4 py-2 w-full border rounded-lg bg-transparent",
if @errors == [] do
"border-gray-500 text-gray-300 placeholder-gray-400"
else
"border-red-600 text-red-600 placeholder-red-600"
end
]}
placeholder="Token"
autofocus
/>
<span
:for={error <- @errors}
class="mt-1 hidden text-red-600 text-sm phx-form-error:block"
>
<span :for={error <- @errors} class="mt-1 text-red-600 text-sm">
<%= translate_error(error) %>
</span>
</div>

View file

@ -41,7 +41,7 @@ defmodule LivebookWeb.AppAuthLive do
</div>
<div class="text-2xl text-gray-800 w-full pt-2">
<form class="flex flex-col space-y-4 items-center" phx-submit="authenticate">
<div phx-feedback-for="password" class={["w-[20ch]", @errors != [] && "show-errors"]}>
<div class="w-[20ch]">
<.text_field
type="password"
name="password"
@ -49,10 +49,7 @@ defmodule LivebookWeb.AppAuthLive do
placeholder="Password"
autofocus
/>
<span
:for={error <- @errors}
class="mt-1 hidden text-red-600 text-sm phx-form-error:block"
>
<span :for={error <- @errors} class="mt-1 text-red-600 text-sm">
<%= translate_error(error) %>
</span>
</div>

View file

@ -120,7 +120,7 @@ defmodule LivebookWeb.Hub.FileSystemFormComponent do
with {:ok, file_system} <- Ecto.Changeset.apply_action(changeset, :update),
:ok <- check_file_system_connectivity(file_system),
:ok <- save_file_system(file_system, socket) do
:ok <- save_file_system(file_system, changeset, socket) do
message =
case socket.assigns.mode do
:new -> "File storage added successfully"
@ -152,10 +152,18 @@ defmodule LivebookWeb.Hub.FileSystemFormComponent do
end
end
defp save_file_system(file_system, socket) do
case socket.assigns.mode do
:new -> Livebook.Hubs.create_file_system(socket.assigns.hub, file_system)
:edit -> Livebook.Hubs.update_file_system(socket.assigns.hub, file_system)
defp save_file_system(file_system, changeset, socket) do
result =
case socket.assigns.mode do
:new -> Livebook.Hubs.create_file_system(socket.assigns.hub, file_system)
:edit -> Livebook.Hubs.update_file_system(socket.assigns.hub, file_system)
end
with {:error, errors} <- result do
{:error,
changeset
|> Livebook.Utils.put_changeset_errors(errors)
|> Map.replace!(:action, :validate)}
end
end

View file

@ -249,8 +249,6 @@ defmodule LivebookWeb.Hub.NewLive do
|> assign_form(changeset)}
{:error, changeset} ->
changeset = Map.replace!(changeset, :action, :validate)
{:noreply, assign_form(socket, changeset)}
{:transport_error, message} ->

View file

@ -7,25 +7,23 @@ defmodule LivebookWeb.Hub.SecretFormComponent do
@impl true
def mount(socket) do
{:ok, assign(socket, changeset: nil, error_message: nil)}
{:ok, assign(socket, error_message: nil)}
end
@impl true
def update(assigns, socket) do
changeset =
socket.assigns.changeset ||
Secrets.change_secret(%Secret{}, %{
name: assigns.secret_name,
value: assigns.secret_value
})
{:ok,
socket
|> assign(assigns)
|> assign_new(:changeset, fn ->
Secrets.change_secret(%Secret{}, %{
name: assigns.secret_name,
value: assigns.secret_value
})
end)
|> assign(
title: title(socket),
button: button_attrs(socket),
changeset: changeset,
deployment_group_id: assigns[:deployment_group_id]
)}
end
@ -91,8 +89,10 @@ defmodule LivebookWeb.Hub.SecretFormComponent do
@impl true
def handle_event("save", %{"secret" => attrs}, socket) do
with {:ok, secret} <- Secrets.update_secret(%Secret{}, attrs),
:ok <- set_secret(socket, secret) do
changeset = Secrets.change_secret(%Secret{}, attrs)
with {:ok, secret} <- Ecto.Changeset.apply_action(changeset, :insert),
:ok <- save_secret(socket, secret, changeset) do
message =
if socket.assigns.secret_name,
do: "Secret #{secret.name} updated successfully",
@ -103,8 +103,8 @@ defmodule LivebookWeb.Hub.SecretFormComponent do
|> put_flash(:success, message)
|> push_patch(to: socket.assigns.return_to)}
else
{:error, changeset} ->
{:noreply, assign(socket, changeset: Map.replace!(changeset, :action, :validate))}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, changeset: changeset)}
{:transport_error, error} ->
{:noreply, assign(socket, error_message: error)}
@ -126,12 +126,20 @@ defmodule LivebookWeb.Hub.SecretFormComponent do
defp button_attrs(%{assigns: %{secret_name: nil}}), do: %{icon: "add-line", label: "Add"}
defp button_attrs(_), do: %{icon: "save-line", label: "Save"}
defp set_secret(%{assigns: %{secret_name: nil}} = socket, %Secret{} = secret) do
Hubs.create_secret(socket.assigns.hub, secret)
end
defp save_secret(socket, secret, changeset) do
result =
if socket.assigns.secret_name do
Hubs.update_secret(socket.assigns.hub, secret)
else
Hubs.create_secret(socket.assigns.hub, secret)
end
defp set_secret(socket, %Secret{} = secret) do
Hubs.update_secret(socket.assigns.hub, secret)
with {:error, errors} <- result do
{:error,
changeset
|> Livebook.Utils.put_changeset_errors(errors)
|> Map.replace!(:action, :validate)}
end
end
defp secret_name_input_class(nil), do: "uppercase"

View file

@ -23,7 +23,7 @@ defmodule LivebookWeb.Hub.Teams.DeploymentGroupFormComponent do
%{}
end
{:ok, assign_form(socket, change_deployment_group(socket, attrs))}
{:ok, assign_form(socket, Teams.change_deployment_group(%DeploymentGroup{}, attrs))}
end
end
@ -88,6 +88,8 @@ defmodule LivebookWeb.Hub.Teams.DeploymentGroupFormComponent do
phx-debounce
/>
<.hidden_field field={@form[:hub_id]} value={@hub.id} />
<LivebookWeb.AppComponents.deployment_group_form_content hub={@hub} form={@form} />
<div class="flex space-x-2">
@ -145,10 +147,7 @@ defmodule LivebookWeb.Hub.Teams.DeploymentGroupFormComponent do
@impl true
def handle_event("save", %{"deployment_group" => attrs}, socket) do
changeset = change_deployment_group(socket, attrs)
with {:ok, deployment_group} <- Ecto.Changeset.apply_action(changeset, :update),
{:ok, _id} <- Teams.create_deployment_group(socket.assigns.hub, deployment_group) do
with {:ok, _deployment_group} <- Teams.create_deployment_group(socket.assigns.hub, attrs) do
if return_to = socket.assigns.return_to do
{:noreply,
socket
@ -159,7 +158,7 @@ defmodule LivebookWeb.Hub.Teams.DeploymentGroupFormComponent do
end
else
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign_form(socket, Map.replace!(changeset, :action, :validate))}
{:noreply, assign_form(socket, changeset)}
{:transport_error, message} ->
{:noreply, assign(socket, error_message: message)}
@ -171,14 +170,9 @@ defmodule LivebookWeb.Hub.Teams.DeploymentGroupFormComponent do
def handle_event("validate", %{"deployment_group" => attrs}, socket) do
changeset =
change_deployment_group(socket, attrs)
Teams.change_deployment_group(%DeploymentGroup{}, attrs)
|> Map.replace!(:action, :validate)
{:noreply, assign_form(socket, changeset)}
end
defp change_deployment_group(socket, attrs) do
%DeploymentGroup{hub_id: socket.assigns.hub.id}
|> Teams.change_deployment_group(attrs)
end
end

View file

@ -216,14 +216,18 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
@impl true
def handle_event("save", %{"secret" => attrs}, socket) do
with {:ok, secret} <- Secrets.update_secret(%Secret{}, attrs),
:ok <- set_secret(socket, secret) do
changeset = Secrets.change_secret(%Secret{}, attrs)
with {:ok, secret} <- Ecto.Changeset.apply_action(changeset, :insert),
:ok <- save_secret(socket, secret, changeset) do
Session.set_secret(socket.assigns.session.pid, secret)
{:noreply,
socket
|> push_patch(to: socket.assigns.return_to)
|> push_secret_selected(secret.name)}
else
{:error, changeset} ->
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, changeset: changeset)}
{:transport_error, error} ->
@ -271,13 +275,16 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
defp title(%{assigns: %{select_secret_metadata: %{options: %{"title" => title}}}}), do: title
defp title(_), do: "Select secret"
defp set_secret(socket, %Secret{hub_id: nil} = secret) do
Session.set_secret(socket.assigns.session.pid, secret)
end
defp set_secret(socket, %Secret{} = secret) do
with :ok <- Hubs.create_secret(socket.assigns.hub, secret) do
Session.set_secret(socket.assigns.session.pid, secret)
defp save_secret(socket, secret, changeset) do
if secret.hub_id do
with {:error, errors} <- Hubs.create_secret(socket.assigns.hub, secret) do
{:error,
changeset
|> Livebook.Utils.put_changeset_errors(errors)
|> Map.replace!(:action, :validate)}
end
else
:ok
end
end

View file

@ -99,7 +99,7 @@ defmodule Livebook.MixProject do
defp deps do
[
{:phoenix, "~> 1.7.8"},
{:phoenix_live_view, "~> 0.20.2"},
{:phoenix_live_view, "~> 1.0.0-rc.0", override: true},
{:phoenix_html, "~> 4.0"},
{:phoenix_live_dashboard, "~> 0.8.0"},
{:telemetry_metrics, "~> 1.0"},

View file

@ -36,7 +36,7 @@
"phoenix_html": {:hex, :phoenix_html, "4.1.1", "4c064fd3873d12ebb1388425a8f2a19348cef56e7289e1998e2d2fa758aa982e", [:mix], [], "hexpm", "f2f2df5a72bc9a2f510b21497fd7d2b86d932ec0598f0210fed4114adc546c6f"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.3", "7ff51c9b6609470f681fbea20578dede0e548302b0c8bdf338b5a753a4f045bf", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "f9470a0a8bae4f56430a23d42f977b5a6205fdba6559d76f932b876bfaec652d"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.5.3", "f2161c207fda0e4fb55165f650f7f8db23f02b29e3bff00ff7ef161d6ac1f09d", [:mix], [{:file_system, "~> 0.3 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b4ec9cd73cb01ff1bd1cac92e045d13e7030330b74164297d1aee3907b54803c"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.20.14", "70fa101aa0539e81bed4238777498f6215e9dda3461bdaa067cad6908110c364", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "82f6d006c5264f979ed5eb75593d808bbe39020f20df2e78426f4f2d570e2402"},
"phoenix_live_view": {:hex, :phoenix_live_view, "1.0.0-rc.0", "42a5a568d7105d1d4e6542badfe061154491da584836a4a4ead148a2c67d649e", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "10cb32643a0cfecb6f3f1feff8211273ba5917891e451b635abc940e3b907a4a"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
"plug": {:git, "https://github.com/elixir-plug/plug.git", "0574733fb933e4a2ea78532e38e687d9cffb4858", []},

View file

@ -70,21 +70,20 @@ defmodule Livebook.HubsTest do
value = hub.id
secret = build(:secret, name: name, value: value, hub_id: hub.id)
assert Hubs.create_secret(hub, secret) == :ok
assert :ok = Hubs.create_secret(hub, secret)
assert secret in Hubs.get_secrets(hub)
# Guarantee uniqueness
assert {:error, changeset} = Hubs.create_secret(hub, secret)
assert "has already been taken" in errors_on(changeset).name
assert {:error, errors} = Hubs.create_secret(hub, secret)
assert "has already been taken" in errors[:name]
end
test "returns changeset errors when data is invalid", %{user: user, node: node} do
hub = connect_to_teams(user, node)
secret = build(:secret, name: "LB_FOO", value: "BAR", hub_id: hub.id)
assert {:error, changeset} = Hubs.create_secret(hub, secret)
assert "cannot start with the LB_ prefix" in errors_on(changeset).name
refute secret in Hubs.get_secrets(hub)
assert {:error, errors} = Hubs.create_secret(hub, secret)
assert "cannot start with the LB_ prefix" in errors[:name]
end
end
@ -119,8 +118,8 @@ defmodule Livebook.HubsTest do
updated_secret = Map.replace!(secret, :value, "")
assert {:error, changeset} = Hubs.update_secret(hub, updated_secret)
assert "can't be blank" in errors_on(changeset).value
assert {:error, errors} = Hubs.update_secret(hub, updated_secret)
assert "can't be blank" in errors[:value]
end
end
@ -156,16 +155,16 @@ defmodule Livebook.HubsTest do
assert_receive {:file_system_created, %{bucket_url: ^bucket_url, region: ^region}}
# Guarantee uniqueness
assert {:error, changeset} = Hubs.create_file_system(hub, file_system)
assert "has already been taken" in errors_on(changeset).bucket_url
assert {:error, errors} = Hubs.create_file_system(hub, file_system)
assert "has already been taken" in errors[:bucket_url]
end
test "returns changeset errors when data is invalid", %{user: user, node: node} do
hub = connect_to_teams(user, node)
file_system = build(:fs_s3, bucket_url: nil)
assert {:error, changeset} = Hubs.create_file_system(hub, file_system)
assert "can't be blank" in errors_on(changeset).bucket_url
assert {:error, errors} = Hubs.create_file_system(hub, file_system)
assert "can't be blank" in errors[:bucket_url]
end
end
@ -202,8 +201,8 @@ defmodule Livebook.HubsTest do
update_file_system = Map.replace!(file_system, :bucket_url, "")
assert {:error, changeset} = Hubs.update_file_system(hub, update_file_system)
assert "can't be blank" in errors_on(changeset).bucket_url
assert {:error, errors} = Hubs.update_file_system(hub, update_file_system)
assert "can't be blank" in errors[:bucket_url]
end
end

View file

@ -170,34 +170,32 @@ defmodule Livebook.TeamsTest do
test "creates a new deployment group when the data is valid", %{user: user, node: node} do
team = connect_to_teams(user, node)
deployment_group =
build(:deployment_group, name: "DEPLOYMENT_GROUP_#{team.id}", mode: :online)
attrs = params_for(:deployment_group, name: "DEPLOYMENT_GROUP_#{team.id}", mode: :online)
assert {:ok, id} = Teams.create_deployment_group(team, deployment_group)
assert {:ok, deployment_group} = Teams.create_deployment_group(team, attrs)
%{name: name, mode: mode} = deployment_group
id = to_string(id)
%{id: id, name: name, mode: mode} = deployment_group
assert_receive {:deployment_group_created, %{id: ^id, name: ^name, mode: ^mode}}
# Guarantee uniqueness
assert {:error, changeset} = Teams.create_deployment_group(team, deployment_group)
assert {:error, changeset} = Teams.create_deployment_group(team, attrs)
assert "has already been taken" in errors_on(changeset).name
end
test "returns changeset errors when the name is invalid", %{user: user, node: node} do
team = connect_to_teams(user, node)
deployment_group = %{build(:deployment_group) | name: ""}
attrs = params_for(:deployment_group, name: "")
assert {:error, changeset} = Teams.create_deployment_group(team, deployment_group)
assert {:error, changeset} = Teams.create_deployment_group(team, attrs)
assert "can't be blank" in errors_on(changeset).name
end
test "returns changeset errors when the mode is invalid", %{user: user, node: node} do
team = connect_to_teams(user, node)
deployment_group = %{build(:deployment_group) | mode: "invalid"}
attrs = params_for(:deployment_group, mode: "invalid")
assert {:error, changeset} = Teams.create_deployment_group(team, deployment_group)
assert {:error, changeset} = Teams.create_deployment_group(team, attrs)
assert "is invalid" in errors_on(changeset).mode
end
end
@ -206,10 +204,9 @@ defmodule Livebook.TeamsTest do
@tag :tmp_dir
test "deploys app to Teams from a notebook", %{user: user, node: node, tmp_dir: tmp_dir} do
team = connect_to_teams(user, node)
deployment_group = build(:deployment_group, name: "BAZ", mode: :online)
{:ok, id} = Teams.create_deployment_group(team, deployment_group)
attrs = params_for(:deployment_group, name: "BAZ", mode: :online)
{:ok, %{id: id}} = Teams.create_deployment_group(team, attrs)
id = to_string(id)
assert_receive {:deployment_group_created, %{id: ^id}}
# creates the app deployment

View file

@ -147,11 +147,10 @@ defmodule Livebook.Factory do
end
def insert_deployment_group(attrs \\ %{}) do
deployment_group = build(:deployment_group, attrs)
hub = Livebook.Hubs.fetch_hub!(deployment_group.hub_id)
{:ok, id} = Livebook.Teams.create_deployment_group(hub, deployment_group)
%{deployment_group | id: to_string(id)}
attrs = params_for(:deployment_group, attrs)
hub = Livebook.Hubs.fetch_hub!(attrs.hub_id)
{:ok, deployment_group} = Livebook.Teams.create_deployment_group(hub, attrs)
deployment_group
end
def insert_env_var(factory_name, attrs \\ %{}) do