mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-10-10 13:38:09 +08:00
Show link to deployed apps after deploy (#2658)
This commit is contained in:
parent
ca30cbc509
commit
22abd815eb
6 changed files with 62 additions and 23 deletions
|
@ -274,6 +274,11 @@ defmodule Livebook.Hubs.TeamClient do
|
||||||
{:noreply, handle_event(topic, data, state)}
|
{:noreply, handle_event(topic, data, state)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_info({:apps_manager_status, _}, state)
|
||||||
|
when not state.connected? or state.deployment_group_id == nil do
|
||||||
|
{:noreply, state}
|
||||||
|
end
|
||||||
|
|
||||||
def handle_info({:apps_manager_status, entries}, %{hub: %{id: id}} = state) do
|
def handle_info({:apps_manager_status, entries}, %{hub: %{id: id}} = state) do
|
||||||
app_deployment_statuses =
|
app_deployment_statuses =
|
||||||
for %{app_spec: %Apps.TeamsAppSpec{hub_id: ^id} = app_spec, running?: running?} <- entries do
|
for %{app_spec: %Apps.TeamsAppSpec{hub_id: ^id} = app_spec, running?: running?} <- entries do
|
||||||
|
|
|
@ -9,7 +9,7 @@ defmodule Livebook.Teams.Connection do
|
||||||
@no_state :no_state
|
@no_state :no_state
|
||||||
@loop_ping_delay 5_000
|
@loop_ping_delay 5_000
|
||||||
|
|
||||||
@websocket_messages [:ssl, :tcp, :ssl_closed, :tcp_closed, :ssl_error, :tcp_error]
|
@expected_messages [:ssl, :tcp, :ssl_closed, :tcp_closed, :ssl_error, :tcp_error]
|
||||||
|
|
||||||
defstruct [:listener, :headers, :http_conn, :websocket, :ref]
|
defstruct [:listener, :headers, :http_conn, :websocket, :ref]
|
||||||
|
|
||||||
|
@ -39,13 +39,13 @@ defmodule Livebook.Teams.Connection do
|
||||||
@impl true
|
@impl true
|
||||||
def handle_event(event_type, event_data, state, data)
|
def handle_event(event_type, event_data, state, data)
|
||||||
|
|
||||||
def handle_event(:internal, :connect, @no_state, %__MODULE__{} = data) do
|
def handle_event(:internal, :connect, @no_state, data) do
|
||||||
case WebSocket.connect(data.headers) do
|
case WebSocket.connect(data.headers) do
|
||||||
{:ok, conn, websocket, ref} ->
|
{:ok, conn, websocket, ref} ->
|
||||||
send(data.listener, :connected)
|
send(data.listener, :connected)
|
||||||
send(self(), {:loop_ping, ref})
|
send(self(), {:loop_ping, ref})
|
||||||
|
|
||||||
{:keep_state, %__MODULE__{data | http_conn: conn, ref: ref, websocket: websocket}}
|
{:keep_state, %{data | http_conn: conn, ref: ref, websocket: websocket}}
|
||||||
|
|
||||||
{:transport_error, reason} ->
|
{:transport_error, reason} ->
|
||||||
send(data.listener, {:connection_error, reason})
|
send(data.listener, {:connection_error, reason})
|
||||||
|
@ -63,14 +63,14 @@ defmodule Livebook.Teams.Connection do
|
||||||
{:keep_state_and_data, {:next_event, :internal, :connect}}
|
{:keep_state_and_data, {:next_event, :internal, :connect}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event(:info, {:loop_ping, ref}, @no_state, %__MODULE__{ref: ref} = data) do
|
def handle_event(:info, {:loop_ping, ref}, @no_state, %{ref: ref} = data) do
|
||||||
case WebSocket.send(data.http_conn, data.websocket, data.ref, :ping) do
|
case WebSocket.send(data.http_conn, data.websocket, data.ref, :ping) do
|
||||||
{:ok, conn, websocket} ->
|
{:ok, conn, websocket} ->
|
||||||
Process.send_after(self(), {:loop_ping, data.ref}, @loop_ping_delay)
|
Process.send_after(self(), {:loop_ping, data.ref}, @loop_ping_delay)
|
||||||
{:keep_state, %__MODULE__{data | http_conn: conn, websocket: websocket}}
|
{:keep_state, %{data | http_conn: conn, websocket: websocket}}
|
||||||
|
|
||||||
{:error, conn, websocket, _reason} ->
|
{:error, conn, websocket, _reason} ->
|
||||||
{:keep_state, %__MODULE__{data | http_conn: conn, websocket: websocket}}
|
{:keep_state, %{data | http_conn: conn, websocket: websocket}}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -78,11 +78,15 @@ defmodule Livebook.Teams.Connection do
|
||||||
:keep_state_and_data
|
:keep_state_and_data
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event(:info, message, @no_state, %__MODULE__{} = data)
|
def handle_event(:info, message, @no_state, data) when elem(message, 0) in @expected_messages do
|
||||||
when elem(message, 0) in @websocket_messages do
|
|
||||||
handle_websocket_message(message, data)
|
handle_websocket_message(message, data)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_event(:info, message, @no_state, %{http_conn: nil})
|
||||||
|
when elem(message, 0) in @expected_messages do
|
||||||
|
:keep_state_and_data
|
||||||
|
end
|
||||||
|
|
||||||
def handle_event(:info, _message, @no_state, _data) do
|
def handle_event(:info, _message, @no_state, _data) do
|
||||||
:keep_state_and_data
|
:keep_state_and_data
|
||||||
end
|
end
|
||||||
|
@ -94,7 +98,7 @@ defmodule Livebook.Teams.Connection do
|
||||||
{:keep_state, %{data | http_conn: conn, websocket: websocket}}
|
{:keep_state, %{data | http_conn: conn, websocket: websocket}}
|
||||||
|
|
||||||
{:error, conn, websocket, reason} ->
|
{:error, conn, websocket, reason} ->
|
||||||
data = %__MODULE__{data | http_conn: conn, websocket: websocket}
|
data = %{data | http_conn: conn, websocket: websocket}
|
||||||
send(data.listener, {:connection_error, reason})
|
send(data.listener, {:connection_error, reason})
|
||||||
:gen_statem.reply(from, {:error, reason})
|
:gen_statem.reply(from, {:error, reason})
|
||||||
|
|
||||||
|
@ -104,10 +108,10 @@ defmodule Livebook.Teams.Connection do
|
||||||
|
|
||||||
# Private
|
# Private
|
||||||
|
|
||||||
defp handle_websocket_message(message, %__MODULE__{} = data) do
|
defp handle_websocket_message(message, data) do
|
||||||
case WebSocket.receive(data.http_conn, data.ref, data.websocket, message) do
|
case WebSocket.receive(data.http_conn, data.ref, data.websocket, message) do
|
||||||
{:ok, conn, websocket, binaries} ->
|
{:ok, conn, websocket, binaries} ->
|
||||||
data = %__MODULE__{data | http_conn: conn, websocket: websocket}
|
data = %{data | http_conn: conn, websocket: websocket}
|
||||||
|
|
||||||
for binary <- binaries do
|
for binary <- binaries do
|
||||||
%{type: {topic, message}} = LivebookProto.Event.decode(binary)
|
%{type: {topic, message}} = LivebookProto.Event.decode(binary)
|
||||||
|
@ -118,7 +122,7 @@ defmodule Livebook.Teams.Connection do
|
||||||
|
|
||||||
{:error, conn, websocket, reason} ->
|
{:error, conn, websocket, reason} ->
|
||||||
send(data.listener, {:connection_error, reason})
|
send(data.listener, {:connection_error, reason})
|
||||||
data = %__MODULE__{data | http_conn: conn, websocket: websocket}
|
data = %{data | http_conn: conn, websocket: websocket}
|
||||||
|
|
||||||
{:keep_state, data, {:next_event, :internal, :connect}}
|
{:keep_state, data, {:next_event, :internal, :connect}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -108,7 +108,13 @@ defmodule LivebookWeb.CoreComponents do
|
||||||
|
|
||||||
slot :inner_block
|
slot :inner_block
|
||||||
|
|
||||||
|
def message_box(assigns)
|
||||||
|
|
||||||
def message_box(assigns) do
|
def message_box(assigns) do
|
||||||
|
if assigns.message && assigns.inner_block != [] do
|
||||||
|
raise ArgumentError, "expected either message or inner_block, got both."
|
||||||
|
end
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class={[
|
<div class={[
|
||||||
"shadow text-sm flex items-center space-x-3 rounded-lg px-4 py-2 border-l-4 rounded-l-none bg-white text-gray-700",
|
"shadow text-sm flex items-center space-x-3 rounded-lg px-4 py-2 border-l-4 rounded-l-none bg-white text-gray-700",
|
||||||
|
@ -122,9 +128,9 @@ defmodule LivebookWeb.CoreComponents do
|
||||||
class="whitespace-pre-wrap pr-2 max-h-52 overflow-y-auto tiny-scrollbar"
|
class="whitespace-pre-wrap pr-2 max-h-52 overflow-y-auto tiny-scrollbar"
|
||||||
phx-no-format
|
phx-no-format
|
||||||
><%= @message %></div>
|
><%= @message %></div>
|
||||||
<div :if={@inner_block}>
|
<%= if @inner_block != [] do %>
|
||||||
<%= render_slot(@inner_block) %>
|
<%= render_slot(@inner_block) %>
|
||||||
</div>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
|
@ -67,7 +67,19 @@ defmodule LivebookWeb.SessionLive.AppTeamsLive do
|
||||||
|
|
||||||
<div :if={@messages != []} class="flex flex-col gap-2">
|
<div :if={@messages != []} class="flex flex-col gap-2">
|
||||||
<.message_box :for={{kind, message} <- @messages} kind={kind}>
|
<.message_box :for={{kind, message} <- @messages} kind={kind}>
|
||||||
<%= raw(message) %>
|
<div class="flex flex-auto items-center justify-between">
|
||||||
|
<span class="whitespace-pre-wrap"><%= raw(message) %></span>
|
||||||
|
|
||||||
|
<.link
|
||||||
|
:if={kind == :success}
|
||||||
|
href={"#{Livebook.Config.teams_url()}/orgs/#{@hub.org_id}"}
|
||||||
|
target="_blank"
|
||||||
|
class="font-medium text-blue-600"
|
||||||
|
>
|
||||||
|
<span>See all deployed apps</span>
|
||||||
|
<.remix_icon icon="external-link-line" />
|
||||||
|
</.link>
|
||||||
|
</div>
|
||||||
</.message_box>
|
</.message_box>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -296,7 +308,7 @@ defmodule LivebookWeb.SessionLive.AppTeamsLive do
|
||||||
<div class="flex gap-2 items-center text-gray-700">
|
<div class="flex gap-2 items-center text-gray-700">
|
||||||
<h3 class="text-sm">
|
<h3 class="text-sm">
|
||||||
<span class="font-semibold"><%= @deployment_group.name %></span>
|
<span class="font-semibold"><%= @deployment_group.name %></span>
|
||||||
(internal-domain.example.com)
|
<span :if={url = @deployment_group.url}>(<%= url %>)</span>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
|
|
|
@ -43,7 +43,14 @@ defmodule Livebook.Integration.AppsTest do
|
||||||
Apps.Deployer.deploy_monitor(deployer_pid, app_spec)
|
Apps.Deployer.deploy_monitor(deployer_pid, app_spec)
|
||||||
|
|
||||||
assert_receive {:app_created, %{pid: pid, slug: ^slug}}
|
assert_receive {:app_created, %{pid: pid, slug: ^slug}}
|
||||||
assert_receive {:app_updated, %{slug: ^slug, sessions: [session]}}
|
|
||||||
|
assert_receive {:app_updated,
|
||||||
|
%{
|
||||||
|
slug: ^slug,
|
||||||
|
sessions: [
|
||||||
|
%{app_status: %{execution: :executed, lifecycle: :active}} = session
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
|
||||||
assert %{secrets: %{^secret_name => ^secret}} = Livebook.Session.get_data(session.pid)
|
assert %{secrets: %{^secret_name => ^secret}} = Livebook.Session.get_data(session.pid)
|
||||||
|
|
||||||
|
|
|
@ -546,14 +546,18 @@ defmodule LivebookWeb.Integration.SessionLiveTest do
|
||||||
|
|
||||||
assert render(view) =~
|
assert render(view) =~
|
||||||
"App deployment created successfully"
|
"App deployment created successfully"
|
||||||
|
|
||||||
|
assert render(view) =~ "#{Livebook.Config.teams_url()}/orgs/#{team.org_id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "deployment flow with existing deployment groups in the hub",
|
test "deployment flow with existing deployment groups in the hub",
|
||||||
%{conn: conn, user: user, node: node, session: session} do
|
%{conn: conn, user: user, node: node, session: session} do
|
||||||
|
Livebook.Teams.Broadcasts.subscribe([:deployment_groups])
|
||||||
team = create_team_hub(user, node)
|
team = create_team_hub(user, node)
|
||||||
Session.set_notebook_hub(session.pid, team.id)
|
Session.set_notebook_hub(session.pid, team.id)
|
||||||
|
|
||||||
deployment_group = insert_deployment_group(mode: :online, hub_id: team.id)
|
id = insert_deployment_group(mode: :online, hub_id: team.id).id
|
||||||
|
assert_receive {:deployment_group_created, %{id: ^id} = deployment_group}
|
||||||
|
|
||||||
{:ok, view, _} = live(conn, ~p"/sessions/#{session.id}")
|
{:ok, view, _} = live(conn, ~p"/sessions/#{session.id}")
|
||||||
|
|
||||||
|
@ -581,9 +585,7 @@ defmodule LivebookWeb.Integration.SessionLiveTest do
|
||||||
|> element(~s/[phx-click="select_deployment_group"][phx-value-id="#{deployment_group.id}"]/)
|
|> element(~s/[phx-click="select_deployment_group"][phx-value-id="#{deployment_group.id}"]/)
|
||||||
|> render_click()
|
|> render_click()
|
||||||
|
|
||||||
%{id: id} = deployment_group
|
|
||||||
assert_receive {:operation, {:set_notebook_deployment_group, _, ^id}}
|
assert_receive {:operation, {:set_notebook_deployment_group, _, ^id}}
|
||||||
|
|
||||||
assert render(view) =~ "The selected deployment group has no app servers."
|
assert render(view) =~ "The selected deployment group has no app servers."
|
||||||
|
|
||||||
view
|
view
|
||||||
|
@ -593,7 +595,6 @@ defmodule LivebookWeb.Integration.SessionLiveTest do
|
||||||
# Step: agent instance setup
|
# Step: agent instance setup
|
||||||
|
|
||||||
assert render(view) =~ "Step: add app server"
|
assert render(view) =~ "Step: add app server"
|
||||||
|
|
||||||
assert render(view) =~ "Awaiting an app server to be set up."
|
assert render(view) =~ "Awaiting an app server to be set up."
|
||||||
|
|
||||||
[deployment_group] = Livebook.Hubs.TeamClient.get_deployment_groups(team.id)
|
[deployment_group] = Livebook.Hubs.TeamClient.get_deployment_groups(team.id)
|
||||||
|
@ -613,6 +614,7 @@ defmodule LivebookWeb.Integration.SessionLiveTest do
|
||||||
|
|
||||||
test "shows an error when the deployment size is higher than the maximum size of 20MB",
|
test "shows an error when the deployment size is higher than the maximum size of 20MB",
|
||||||
%{conn: conn, user: user, node: node, session: session} do
|
%{conn: conn, user: user, node: node, session: session} do
|
||||||
|
Livebook.Teams.Broadcasts.subscribe([:deployment_groups])
|
||||||
team = create_team_hub(user, node)
|
team = create_team_hub(user, node)
|
||||||
Session.set_notebook_hub(session.pid, team.id)
|
Session.set_notebook_hub(session.pid, team.id)
|
||||||
|
|
||||||
|
@ -620,8 +622,11 @@ defmodule LivebookWeb.Integration.SessionLiveTest do
|
||||||
app_settings = %{Livebook.Notebook.AppSettings.new() | slug: slug}
|
app_settings = %{Livebook.Notebook.AppSettings.new() | slug: slug}
|
||||||
Session.set_app_settings(session.pid, app_settings)
|
Session.set_app_settings(session.pid, app_settings)
|
||||||
|
|
||||||
deployment_group = insert_deployment_group(mode: :online, hub_id: team.id)
|
id = insert_deployment_group(mode: :online, hub_id: team.id).id
|
||||||
Session.set_notebook_deployment_group(session.pid, deployment_group.id)
|
assert_receive {:deployment_group_created, %{id: ^id}}
|
||||||
|
|
||||||
|
Session.set_notebook_deployment_group(session.pid, id)
|
||||||
|
assert_receive {:operation, {:set_notebook_deployment_group, _, ^id}}
|
||||||
|
|
||||||
%{files_dir: files_dir} = session
|
%{files_dir: files_dir} = session
|
||||||
image_file = FileSystem.File.resolve(files_dir, "image.jpg")
|
image_file = FileSystem.File.resolve(files_dir, "image.jpg")
|
||||||
|
|
Loading…
Add table
Reference in a new issue