From 22abd815eb4e8c7b27dd6f42b9e63eb1e2f2021a Mon Sep 17 00:00:00 2001 From: Alexandre de Souza Date: Tue, 18 Jun 2024 10:12:20 -0300 Subject: [PATCH] Show link to deployed apps after deploy (#2658) --- lib/livebook/hubs/team_client.ex | 5 ++++ lib/livebook/teams/connection.ex | 28 +++++++++++-------- .../components/core_components.ex | 10 +++++-- .../live/session_live/app_teams_live.ex | 16 +++++++++-- test/livebook_teams/apps_test.exs | 9 +++++- test/livebook_teams/web/session_live_test.exs | 17 +++++++---- 6 files changed, 62 insertions(+), 23 deletions(-) diff --git a/lib/livebook/hubs/team_client.ex b/lib/livebook/hubs/team_client.ex index dc06bf5edac5..275d83d2d4aa 100644 --- a/lib/livebook/hubs/team_client.ex +++ b/lib/livebook/hubs/team_client.ex @@ -274,6 +274,11 @@ defmodule Livebook.Hubs.TeamClient do {:noreply, handle_event(topic, data, state)} 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 app_deployment_statuses = for %{app_spec: %Apps.TeamsAppSpec{hub_id: ^id} = app_spec, running?: running?} <- entries do diff --git a/lib/livebook/teams/connection.ex b/lib/livebook/teams/connection.ex index 3747e15242c7..6aa70a4bcffb 100644 --- a/lib/livebook/teams/connection.ex +++ b/lib/livebook/teams/connection.ex @@ -9,7 +9,7 @@ defmodule Livebook.Teams.Connection do @no_state :no_state @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] @@ -39,13 +39,13 @@ defmodule Livebook.Teams.Connection do @impl true 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 {:ok, conn, websocket, ref} -> send(data.listener, :connected) 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} -> send(data.listener, {:connection_error, reason}) @@ -63,14 +63,14 @@ defmodule Livebook.Teams.Connection do {:keep_state_and_data, {:next_event, :internal, :connect}} 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 {:ok, conn, websocket} -> 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} -> - {:keep_state, %__MODULE__{data | http_conn: conn, websocket: websocket}} + {:keep_state, %{data | http_conn: conn, websocket: websocket}} end end @@ -78,11 +78,15 @@ defmodule Livebook.Teams.Connection do :keep_state_and_data end - def handle_event(:info, message, @no_state, %__MODULE__{} = data) - when elem(message, 0) in @websocket_messages do + def handle_event(:info, message, @no_state, data) when elem(message, 0) in @expected_messages do handle_websocket_message(message, data) 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 :keep_state_and_data end @@ -94,7 +98,7 @@ defmodule Livebook.Teams.Connection do {:keep_state, %{data | http_conn: conn, websocket: websocket}} {: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}) :gen_statem.reply(from, {:error, reason}) @@ -104,10 +108,10 @@ defmodule Livebook.Teams.Connection do # 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 {:ok, conn, websocket, binaries} -> - data = %__MODULE__{data | http_conn: conn, websocket: websocket} + data = %{data | http_conn: conn, websocket: websocket} for binary <- binaries do %{type: {topic, message}} = LivebookProto.Event.decode(binary) @@ -118,7 +122,7 @@ defmodule Livebook.Teams.Connection do {:error, conn, websocket, 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}} end diff --git a/lib/livebook_web/components/core_components.ex b/lib/livebook_web/components/core_components.ex index 26fa876d86d0..8b6e2f0c2b71 100644 --- a/lib/livebook_web/components/core_components.ex +++ b/lib/livebook_web/components/core_components.ex @@ -108,7 +108,13 @@ defmodule LivebookWeb.CoreComponents do slot :inner_block + def message_box(assigns) + def message_box(assigns) do + if assigns.message && assigns.inner_block != [] do + raise ArgumentError, "expected either message or inner_block, got both." + end + ~H"""
<%= @message %>
-
+ <%= if @inner_block != [] do %> <%= render_slot(@inner_block) %> -
+ <% end %> """ end diff --git a/lib/livebook_web/live/session_live/app_teams_live.ex b/lib/livebook_web/live/session_live/app_teams_live.ex index 016bef0b5da5..1009eeb6b60d 100644 --- a/lib/livebook_web/live/session_live/app_teams_live.ex +++ b/lib/livebook_web/live/session_live/app_teams_live.ex @@ -67,7 +67,19 @@ defmodule LivebookWeb.SessionLive.AppTeamsLive do
<.message_box :for={{kind, message} <- @messages} kind={kind}> - <%= raw(message) %> +
+ <%= raw(message) %> + + <.link + :if={kind == :success} + href={"#{Livebook.Config.teams_url()}/orgs/#{@hub.org_id}"} + target="_blank" + class="font-medium text-blue-600" + > + See all deployed apps + <.remix_icon icon="external-link-line" /> + +
@@ -296,7 +308,7 @@ defmodule LivebookWeb.SessionLive.AppTeamsLive do

<%= @deployment_group.name %> - (internal-domain.example.com) + (<%= url %>)

diff --git a/test/livebook_teams/apps_test.exs b/test/livebook_teams/apps_test.exs index 63e9b0712843..b4288bbb5e16 100644 --- a/test/livebook_teams/apps_test.exs +++ b/test/livebook_teams/apps_test.exs @@ -43,7 +43,14 @@ defmodule Livebook.Integration.AppsTest do Apps.Deployer.deploy_monitor(deployer_pid, app_spec) 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) diff --git a/test/livebook_teams/web/session_live_test.exs b/test/livebook_teams/web/session_live_test.exs index 83fc5fa3f0ec..97dfab818576 100644 --- a/test/livebook_teams/web/session_live_test.exs +++ b/test/livebook_teams/web/session_live_test.exs @@ -546,14 +546,18 @@ defmodule LivebookWeb.Integration.SessionLiveTest do assert render(view) =~ "App deployment created successfully" + + assert render(view) =~ "#{Livebook.Config.teams_url()}/orgs/#{team.org_id}" end test "deployment flow with existing deployment groups in the hub", %{conn: conn, user: user, node: node, session: session} do + Livebook.Teams.Broadcasts.subscribe([:deployment_groups]) team = create_team_hub(user, node) 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}") @@ -581,9 +585,7 @@ defmodule LivebookWeb.Integration.SessionLiveTest do |> element(~s/[phx-click="select_deployment_group"][phx-value-id="#{deployment_group.id}"]/) |> render_click() - %{id: id} = deployment_group assert_receive {:operation, {:set_notebook_deployment_group, _, ^id}} - assert render(view) =~ "The selected deployment group has no app servers." view @@ -593,7 +595,6 @@ defmodule LivebookWeb.Integration.SessionLiveTest do # Step: agent instance setup assert render(view) =~ "Step: add app server" - assert render(view) =~ "Awaiting an app server to be set up." [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", %{conn: conn, user: user, node: node, session: session} do + Livebook.Teams.Broadcasts.subscribe([:deployment_groups]) team = create_team_hub(user, node) 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} Session.set_app_settings(session.pid, app_settings) - deployment_group = insert_deployment_group(mode: :online, hub_id: team.id) - Session.set_notebook_deployment_group(session.pid, deployment_group.id) + id = insert_deployment_group(mode: :online, hub_id: team.id).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 image_file = FileSystem.File.resolve(files_dir, "image.jpg")