defmodule LivebookWeb.SessionLive.AppTeamsComponent do
use LivebookWeb, :live_component
@impl true
def update(assigns, socket) do
socket = assign(socket, assigns)
deployment_groups = Livebook.Teams.get_deployment_groups(assigns.hub)
app_deployments = Livebook.Teams.get_app_deployments(assigns.hub)
deployment_group =
if assigns.deployment_group_id do
Enum.find(deployment_groups, &(&1.id == assigns.deployment_group_id))
end
app_deployment =
if deployment_group do
Enum.find(
app_deployments,
&(&1.slug == assigns.app_settings.slug and &1.deployment_group_id == deployment_group.id)
)
end
socket =
socket
|> assign(
settings_valid?: Livebook.Notebook.AppSettings.valid?(socket.assigns.app_settings),
app_deployment: app_deployment,
deployment_groups: deployment_groups,
deployment_group: deployment_group,
deployment_group_form: %{"deployment_group_id" => assigns.deployment_group_id},
deployment_group_id: assigns.deployment_group_id
)
|> assign_new(:messages, fn -> [] end)
{:ok, socket}
end
@impl true
def render(assigns) do
~H"""
App deployment with Livebook Teams
<.content
file={@file}
hub={@hub}
app_deployment={@app_deployment}
deployment_group={@deployment_group}
deployment_groups={@deployment_groups}
deployment_group_form={@deployment_group_form}
deployment_group_id={@deployment_group_id}
session={@session}
messages={@messages}
myself={@myself}
/>
"""
end
defp content(%{settings_valid?: false} = assigns) do
~H"""
To deploy this app, make sure to specify valid settings.
<.link
class="text-blue-600 font-medium"
patch={~p"/sessions/#{@session.id}/settings/app?context=app-teams"}
>
Configure
<.remix_icon icon="arrow-right-line" />
"""
end
defp content(assigns) do
~H"""
You can deploy this app in the to your own cloud using Livebook Teams. To do that, select
which deployment group you want to send this app and then click the button to Deploy.
<.label>Hub
<%= @hub.hub_emoji %>
<%= @hub.hub_name %>
<%= if @deployment_groups do %>
<%= if @deployment_groups != [] do %>
<.form
for={@deployment_group_form}
phx-change="select_deployment_group"
phx-target={@myself}
id="select_deployment_group_form"
>
<.select_field
help={deployment_group_help()}
field={@deployment_group_form[:deployment_group_id]}
options={deployment_group_options(@deployment_groups)}
label="Deployment Group"
name="deployment_group_id"
value={@deployment_group_id}
/>
<% else %>
<.label help={deployment_group_help()}>
Deployment Group
None configured
<% end %>
<% end %>
<.message_box :for={{kind, message} <- @messages} kind={kind}>
<%= raw(message) %>
Current deployed version:
-
Title:
<%= @app_deployment.title %>
-
Deployed by:
<%= @app_deployment.deployed_by %>
-
Deployed at:
<%= @app_deployment.deployed_at %>
<.button
id="deploy-livebook-agent-button"
color="blue"
phx-click="deploy_app"
phx-target={@myself}
>
<.remix_icon icon="rocket-line" /> Deploy to Livebook Agent
"""
end
@impl true
def handle_event("select_deployment_group", %{"deployment_group_id" => id}, socket) do
id = if(id != "", do: id)
Livebook.Session.set_notebook_deployment_group(socket.assigns.session.pid, id)
{:noreply, socket}
end
def handle_event("deploy_app", _, socket) do
notebook = Livebook.Session.get_notebook(socket.assigns.session.pid)
with {:ok, app_deployment} <- pack_app(socket, notebook),
:ok <- deploy_app(socket, app_deployment, notebook.app_settings) do
message =
"App deployment for #{app_deployment.slug} with title #{app_deployment.title} created successfully"
{:noreply, assign(socket, messages: [{:info, message}])}
end
end
defp pack_app(socket, notebook) do
files_dir = socket.assigns.session.files_dir
case Livebook.Teams.AppDeployment.new(notebook, files_dir) do
{:ok, app_deployment} ->
{:ok, app_deployment}
{:warning, warnings} ->
messages = Enum.map(warnings, &{:error, &1})
{:noreply, assign(socket, messages: messages)}
{:error, error} ->
error = "Failed to pack files: #{error}"
{:noreply, assign(socket, messages: [{:error, error}])}
end
end
defp deploy_app(socket, app_deployment, app_settings) do
case Livebook.Teams.deploy_app(socket.assigns.hub, app_deployment, app_settings) do
:ok ->
:ok
{:error, %{errors: errors}} ->
errors = Enum.map(errors, fn {key, error} -> "#{key}: #{normalize_error(error)}" end)
{:noreply, assign(socket, messages: errors)}
{:transport_error, error} ->
{:noreply, assign(socket, messages: [{:error, error}])}
end
end
defp normalize_error({msg, opts}) do
Enum.reduce(opts, msg, fn {key, value}, acc ->
String.replace(acc, "%{#{key}}", to_string(value))
end)
end
defp deployment_group_options(deployment_groups) do
for deployment_group <- [%{name: "none", id: nil}] ++ deployment_groups,
do: {deployment_group.name, deployment_group.id}
end
defp deployment_group_help() do
"Share deployment credentials, secrets, and configuration with deployment groups."
end
end