Migrate HubLive to Phoenix format (#1373)

This commit is contained in:
Alexandre de Souza 2022-08-30 11:32:48 -03:00 committed by GitHub
parent 9b18eb54cc
commit 2cbd81eaf5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 433 additions and 215 deletions

View file

@ -57,7 +57,10 @@ defmodule Livebook.Hubs.Fly do
changeset = changeset(fly, attrs)
if Hubs.hub_exists?(fly.id) do
{:error, add_error(changeset, :application_id, "already exists")}
{:error,
changeset
|> add_error(:application_id, "already exists")
|> Map.replace!(:action, :validate)}
else
with {:ok, struct} <- apply_action(changeset, :insert) do
Hubs.save_hub(struct)
@ -82,7 +85,10 @@ defmodule Livebook.Hubs.Fly do
{:ok, struct}
end
else
{:error, add_error(changeset, :application_id, "does not exists")}
{:error,
changeset
|> add_error(:application_id, "does not exists")
|> Map.replace!(:action, :validate)}
end
end

View file

@ -37,9 +37,28 @@ defmodule Livebook.Hubs.FlyClient do
end
end
defp graphql(access_token, query) do
def fetch_app(%Fly{application_id: app_id, access_token: access_token}) do
query = """
query($appId: String!) {
app(id: $appId) {
id
name
hostname
platformVersion
deployed
status
}
}
"""
with {:ok, body} <- graphql(access_token, query, %{appId: app_id}) do
{:ok, body["app"]}
end
end
defp graphql(access_token, query, input \\ %{}) do
headers = [{"Authorization", "Bearer #{access_token}"}]
body = {"application/json", Jason.encode!(%{query: query})}
body = {"application/json", Jason.encode!(%{query: query, variables: input})}
case HTTP.request(:post, graphql_endpoint(), headers: headers, body: body) do
{:ok, 200, _, body} ->

View file

@ -0,0 +1,134 @@
defmodule LivebookWeb.Hub.Edit.FlyComponent do
use LivebookWeb, :live_component
import Ecto.Changeset, only: [get_field: 2]
alias Livebook.EctoTypes.HexColor
alias Livebook.Hubs.{Fly, FlyClient}
@impl true
def update(assigns, socket) do
changeset = Fly.change_hub(assigns.hub)
{:ok, app} = FlyClient.fetch_app(assigns.hub)
{:ok,
socket
|> assign(assigns)
|> assign(app_url: "https://#{app["hostname"]}", changeset: changeset)}
end
@impl true
def render(assigns) do
~H"""
<div>
<!-- System details -->
<div class="flex flex-col space-y-2 pb-5">
<div class="flex items-center justify-between border border-gray-200 rounded-lg p-4">
<div class="flex items-center space-x-12">
<.labeled_text label="Application ID">
<%= @hub.application_id %>
</.labeled_text>
<.labeled_text label="Type">
Fly
</.labeled_text>
</div>
<a href={@app_url} class="button-base button-outlined-gray" target="_blank">
<.remix_icon icon="dashboard-2-line" class="align-middle mr-1" />
<span>See app on Fly</span>
</a>
</div>
</div>
<div class="flex flex-col space-y-4">
<h2 class="text-xl text-gray-800 font-semibold pb-2 border-b border-gray-200">
General
</h2>
<.form
id={@id}
class="flex flex-col mt-4 space-y-4"
let={f}
for={@changeset}
phx-submit="save"
phx-change="validate"
phx-target={@myself}
phx-debounce="blur"
>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div class="flex flex-col space-y-1">
<h3 class="text-gray-800 font-semibold">
Name
</h3>
<%= text_input(f, :hub_name, class: "input") %>
<%= error_tag(f, :hub_name) %>
</div>
<div class="flex flex-col space-y-1">
<h3 class="text-gray-800 font-semibold">
Color
</h3>
<div class="flex space-x-4 items-center">
<div
class="border-[3px] rounded-lg p-1 flex justify-center items-center"
style={"border-color: #{hub_color(@changeset)}"}
>
<div class="rounded h-5 w-5" style={"background-color: #{hub_color(@changeset)}"} />
</div>
<div class="relative grow">
<%= text_input(f, :hub_color,
class: "input",
spellcheck: "false",
maxlength: 7
) %>
<button
class="icon-button absolute right-2 top-1"
type="button"
phx-click="randomize_color"
phx-target={@myself}
>
<.remix_icon icon="refresh-line" class="text-xl" />
</button>
<%= error_tag(f, :hub_color) %>
</div>
</div>
</div>
</div>
<%= submit("Update Hub",
class: "button-base button-blue",
phx_disable_with: "Updating...",
disabled: not @changeset.valid?
) %>
</.form>
</div>
</div>
"""
end
@impl true
def handle_event("randomize_color", _, socket) do
handle_event("validate", %{"fly" => %{"hub_color" => HexColor.random()}}, socket)
end
def handle_event("save", %{"fly" => params}, socket) do
case Fly.update_hub(socket.assigns.hub, params) do
{:ok, hub} ->
{:noreply,
socket
|> put_flash(:success, "Hub updated successfully")
|> push_redirect(to: Routes.hub_path(socket, :edit, hub.id))}
{:error, changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
end
def handle_event("validate", %{"fly" => attrs}, socket) do
changeset = Fly.change_hub(socket.assigns.hub, attrs)
{:noreply, assign(socket, changeset: changeset)}
end
defp hub_color(changeset), do: get_field(changeset, :hub_color)
end

View file

@ -0,0 +1,44 @@
defmodule LivebookWeb.Hub.EditLive do
use LivebookWeb, :live_view
alias LivebookWeb.{PageHelpers, LayoutHelpers}
alias Livebook.Hubs
alias Livebook.Hubs.Provider
on_mount LivebookWeb.SidebarHook
@impl true
def mount(%{"id" => id}, _session, socket) do
hub = Hubs.fetch_hub!(id)
type = Provider.type(hub)
if type == "local" do
{:ok,
socket |> redirect(to: "/") |> put_flash(:warning, "You can't edit the localhost Hub")}
else
{:ok, assign(socket, hub: hub, type: type, page_title: "Livebook - Hub")}
end
end
@impl true
def render(assigns) do
~H"""
<LayoutHelpers.layout
socket={@socket}
current_page={Routes.hub_path(@socket, :edit, @hub.id)}
current_user={@current_user}
saved_hubs={@saved_hubs}
>
<div class="px-4 sm:px-8 md:px-16 pt-4 sm:py-7 max-w-screen-md mx-auto space-y-8">
<div>
<PageHelpers.title text="Edit Hub" socket={@socket} />
</div>
<%= if @type == "fly" do %>
<.live_component module={LivebookWeb.Hub.Edit.FlyComponent} hub={@hub} id="fly-form" />
<% end %>
</div>
</LayoutHelpers.layout>
"""
end
end

View file

@ -1,4 +1,4 @@
defmodule LivebookWeb.HubLive.FlyComponent do
defmodule LivebookWeb.Hub.New.FlyComponent do
use LivebookWeb, :live_component
import Ecto.Changeset, only: [get_field: 2, add_error: 3]
@ -11,7 +11,12 @@ defmodule LivebookWeb.HubLive.FlyComponent do
{:ok,
socket
|> assign(assigns)
|> load_data()}
|> assign(
changeset: Fly.change_hub(%Fly{}),
selected_app: nil,
select_options: [],
apps: []
)}
end
@impl true
@ -37,7 +42,6 @@ defmodule LivebookWeb.HubLive.FlyComponent do
phx_debounce: "blur",
phx_target: @myself,
value: access_token(@changeset),
disabled: @operation == :edit,
class: "input w-full",
autofocus: true,
spellcheck: "false",
@ -51,10 +55,7 @@ defmodule LivebookWeb.HubLive.FlyComponent do
<h3 class="text-gray-800 font-semibold">
Application
</h3>
<%= select(f, :application_id, @select_options,
class: "input",
disabled: @operation == :edit
) %>
<%= select(f, :application_id, @select_options, class: "input") %>
<%= error_tag(f, :application_id) %>
</div>
@ -99,10 +100,10 @@ defmodule LivebookWeb.HubLive.FlyComponent do
</div>
</div>
<%= submit("Save",
<%= submit("Add Hub",
class: "button-base button-blue",
phx_disable_with: "Saving...",
disabled: not @valid?
phx_disable_with: "Add...",
disabled: not @changeset.valid?
) %>
<% end %>
</.form>
@ -110,29 +111,6 @@ defmodule LivebookWeb.HubLive.FlyComponent do
"""
end
defp load_data(%{assigns: %{operation: :new}} = socket) do
assign(socket,
changeset: Fly.change_hub(%Fly{}),
selected_app: nil,
select_options: [],
apps: [],
valid?: false
)
end
defp load_data(%{assigns: %{operation: :edit, hub: hub}} = socket) do
{:ok, apps} = FlyClient.fetch_apps(hub.access_token)
params = Map.from_struct(hub)
assign(socket,
changeset: Fly.change_hub(hub, params),
selected_app: hub,
select_options: select_options(apps),
apps: apps,
valid?: true
)
end
@impl true
def handle_event("fetch_data", %{"fly" => %{"access_token" => token}}, socket) do
case FlyClient.fetch_apps(token) do
@ -140,13 +118,7 @@ defmodule LivebookWeb.HubLive.FlyComponent do
opts = select_options(apps)
changeset = Fly.change_hub(%Fly{}, %{access_token: token, hub_color: HexColor.random()})
{:noreply,
assign(socket,
changeset: changeset,
valid?: changeset.valid?,
select_options: opts,
apps: apps
)}
{:noreply, assign(socket, changeset: changeset, select_options: opts, apps: apps)}
{:error, _} ->
changeset =
@ -154,13 +126,7 @@ defmodule LivebookWeb.HubLive.FlyComponent do
|> Fly.change_hub(%{access_token: token})
|> add_error(:access_token, "is invalid")
{:noreply,
assign(socket,
changeset: changeset,
valid?: changeset.valid?,
select_options: [],
apps: []
)}
{:noreply, assign(socket, changeset: changeset, select_options: [], apps: [])}
end
end
@ -169,8 +135,17 @@ defmodule LivebookWeb.HubLive.FlyComponent do
end
def handle_event("save", %{"fly" => params}, socket) do
if socket.assigns.valid? do
{:noreply, save_fly(socket, socket.assigns.operation, params)}
if socket.assigns.changeset.valid? do
case Fly.create_hub(socket.assigns.selected_app, params) do
{:ok, hub} ->
{:noreply,
socket
|> put_flash(:success, "Hub added successfully")
|> push_redirect(to: Routes.hub_path(socket, :edit, hub.id))}
{:error, changeset} ->
{:noreply, assign(socket, changeset: changeset)}
end
else
{:noreply, socket}
end
@ -193,12 +168,7 @@ defmodule LivebookWeb.HubLive.FlyComponent do
end
{:noreply,
assign(socket,
changeset: changeset,
valid?: changeset.valid?,
selected_app: selected_app,
select_options: opts
)}
assign(socket, changeset: changeset, selected_app: selected_app, select_options: opts)}
end
defp select_options(hubs, app_id \\ nil) do
@ -216,38 +186,6 @@ defmodule LivebookWeb.HubLive.FlyComponent do
[disabled_option] ++ options
end
defp save_fly(socket, :new, params) do
case Fly.create_hub(socket.assigns.selected_app, params) do
{:ok, hub} ->
changeset = Fly.change_hub(hub, params)
socket
|> assign(changeset: changeset, selected_app: hub, valid?: changeset.valid?)
|> put_flash(:success, "Hub created successfully")
|> push_redirect(to: Routes.hub_path(socket, :edit, hub.id))
{:error, changeset} ->
assign(socket, changeset: %{changeset | action: :validate}, valid?: changeset.valid?)
end
end
defp save_fly(socket, :edit, params) do
id = socket.assigns.selected_app.id
case Fly.update_hub(socket.assigns.selected_app, params) do
{:ok, hub} ->
changeset = Fly.change_hub(hub, params)
socket
|> assign(changeset: changeset, selected_app: hub, valid?: changeset.valid?)
|> put_flash(:success, "Hub updated successfully")
|> push_redirect(to: Routes.hub_path(socket, :edit, id))
{:error, changeset} ->
assign(socket, changeset: %{changeset | action: :validate}, valid?: changeset.valid?)
end
end
defp hub_color(changeset), do: get_field(changeset, :hub_color)
defp access_token(changeset), do: get_field(changeset, :access_token)
end

View file

@ -1,8 +1,6 @@
defmodule LivebookWeb.HubLive do
defmodule LivebookWeb.Hub.NewLive do
use LivebookWeb, :live_view
alias Livebook.Hubs
alias Livebook.Hubs.Provider
alias LivebookWeb.{PageHelpers, LayoutHelpers}
alias Phoenix.LiveView.JS
@ -10,12 +8,7 @@ defmodule LivebookWeb.HubLive do
@impl true
def mount(_params, _session, socket) do
{:ok,
assign(socket,
selected_provider: nil,
hub: nil,
page_title: "Livebook - Hub"
)}
{:ok, assign(socket, selected_type: nil, page_title: "Livebook - Hub")}
end
@impl true
@ -23,16 +16,13 @@ defmodule LivebookWeb.HubLive do
~H"""
<LayoutHelpers.layout
socket={@socket}
current_page={@current_page}
current_page={Routes.hub_path(@socket, :new)}
current_user={@current_user}
saved_hubs={@saved_hubs}
>
<div class="px-4 sm:px-8 md:px-16 pt-4 sm:py-7 max-w-screen-md mx-auto space-y-8">
<div>
<PageHelpers.title
text={if @operation == :new, do: "Add Hub", else: "Edit Hub"}
socket={@socket}
/>
<PageHelpers.title text="Add Hub" socket={@socket} />
<p class="mt-4 text-gray-700">
Manage your Livebooks in the cloud with Hubs.
</p>
@ -44,7 +34,7 @@ defmodule LivebookWeb.HubLive do
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<.card_item id="fly" selected={@selected_provider} title="Fly">
<.card_item id="fly" selected={@selected_type} title="Fly">
<:logo>
<%= Phoenix.HTML.raw(File.read!("static/images/fly.svg")) %>
</:logo>
@ -53,7 +43,7 @@ defmodule LivebookWeb.HubLive do
</:headline>
</.card_item>
<.card_item id="enterprise" selected={@selected_provider} title="Livebook Enterprise">
<.card_item id="enterprise" selected={@selected_type} title="Livebook Enterprise">
<:logo>
<img
src="/images/enterprise.png"
@ -68,22 +58,17 @@ defmodule LivebookWeb.HubLive do
</div>
</div>
<%= if @selected_provider do %>
<%= if @selected_type do %>
<div class="flex flex-col space-y-4">
<h2 class="text-xl text-gray-800 font-semibold pb-2 border-b border-gray-200">
2. Configure your Hub
</h2>
<%= if @selected_provider == "fly" do %>
<.live_component
module={LivebookWeb.HubLive.FlyComponent}
id="fly-form"
operation={@operation}
hub={@hub}
/>
<%= if @selected_type == "fly" do %>
<.live_component module={LivebookWeb.Hub.New.FlyComponent} id="fly-form" />
<% end %>
<%= if @selected_provider == "enterprise" do %>
<%= if @selected_type == "enterprise" do %>
<div>
Livebook Enterprise is currently in closed beta. If you want to learn more, <a
href="https://livebook.dev/#livebook-plans"
@ -104,7 +89,7 @@ defmodule LivebookWeb.HubLive do
<div
id={@id}
class={"flex card-item flex-col " <> card_item_bg_color(@id, @selected)}
phx-click={JS.push("select_provider", value: %{value: @id})}
phx-click={JS.push("select_type", value: %{value: @id})}
>
<div class="flex items-center justify-center card-item-logo p-6 border-2 rounded-t-2xl h-[150px]">
<%= render_slot(@logo) %>
@ -126,30 +111,7 @@ defmodule LivebookWeb.HubLive do
defp card_item_bg_color(_id, _selected), do: ""
@impl true
def handle_params(%{"id" => "local-host"}, _url, socket) do
{:noreply,
socket |> redirect(to: "/") |> put_flash(:warning, "You can't edit the localhost Hub")}
end
def handle_params(%{"id" => id}, _url, socket) do
hub = Hubs.fetch_hub!(id)
provider = Provider.type(hub)
{:noreply,
assign(socket,
operation: :edit,
hub: hub,
selected_provider: provider,
current_page: Routes.hub_path(socket, :edit, hub.id)
)}
end
def handle_params(_params, _url, socket) do
{:noreply, assign(socket, operation: :new, current_page: Routes.hub_path(socket, :new))}
end
@impl true
def handle_event("select_provider", %{"value" => service}, socket) do
{:noreply, assign(socket, selected_provider: service)}
def handle_event("select_type", %{"value" => service}, socket) do
{:noreply, assign(socket, selected_type: service)}
end
end

View file

@ -55,8 +55,8 @@ defmodule LivebookWeb.Router do
live "/explore", ExploreLive, :page
live "/explore/notebooks/:slug", ExploreLive, :notebook
live "/hub", HubLive, :new
live "/hub/:id", HubLive, :edit
live "/hub", Hub.NewLive, :new, as: :hub
live "/hub/:id", Hub.EditLive, :edit, as: :hub
live "/sessions/:id", SessionLive, :page
live "/sessions/:id/shortcuts", SessionLive, :shortcuts

View file

@ -1,5 +1,5 @@
defmodule Livebook.Hubs.FlyClientTest do
use ExUnit.Case
use Livebook.DataCase
alias Livebook.Hubs.{Fly, FlyClient}
@ -68,4 +68,42 @@ defmodule Livebook.Hubs.FlyClientTest do
assert {:error, "request failed with code: UNAUTHORIZED"} = FlyClient.fetch_apps("foo")
end
end
describe "fetch_app/1" do
test "fetches an application", %{bypass: bypass} do
app = %{
"id" => "foo-app",
"name" => "foo-app",
"hostname" => "foo-app.fly.dev",
"platformVersion" => "nomad",
"deployed" => true,
"status" => "running"
}
response = %{"data" => %{"app" => app}}
Bypass.expect_once(bypass, "POST", "/", fn conn ->
conn
|> Plug.Conn.put_resp_content_type("application/json")
|> Plug.Conn.resp(200, Jason.encode!(response))
end)
hub = build(:fly)
assert {:ok, ^app} = FlyClient.fetch_app(hub)
end
test "returns unauthorized when token is invalid", %{bypass: bypass} do
error = %{"extensions" => %{"code" => "UNAUTHORIZED"}}
response = %{"data" => nil, "errors" => [error]}
Bypass.expect_once(bypass, "POST", "/", fn conn ->
conn
|> Plug.Conn.put_resp_content_type("application/json")
|> Plug.Conn.resp(200, Jason.encode!(response))
end)
hub = build(:fly)
assert {:error, "request failed with code: UNAUTHORIZED"} = FlyClient.fetch_app(hub)
end
end
end

View file

@ -0,0 +1,104 @@
defmodule LivebookWeb.Hub.EditLiveTest do
use LivebookWeb.ConnCase
import Phoenix.LiveViewTest
alias Livebook.Hubs
setup do
on_exit(&Hubs.clean_hubs/0)
:ok
end
describe "fly" do
test "updates fly", %{conn: conn} do
hub = insert_hub(:fly, id: "fly-987654321", application_id: "987654321")
fly_bypass(hub.application_id)
{:ok, view, html} = live(conn, Routes.hub_path(conn, :edit, hub.id))
assert html =~ "See app on Fly"
assert html =~ "https://#{hub.application_id}.fly.dev"
attrs = %{
"hub_name" => "Personal Hub",
"hub_color" => "#FF00FF"
}
view
|> element("#fly-form")
|> render_change(%{"fly" => attrs})
refute view
|> element("#fly-form .invalid-feedback")
|> has_element?()
assert {:ok, view, _html} =
view
|> element("#fly-form")
|> render_submit(%{"fly" => attrs})
|> follow_redirect(conn)
assert render(view) =~ "Hub updated successfully"
assert view
|> element("#hubs")
|> render() =~ ~s/style="color: #FF00FF"/
assert view
|> element("#hubs")
|> render() =~ "/hub/fly-987654321"
assert view
|> element("#hubs")
|> render() =~ "Personal Hub"
refute Hubs.fetch_hub!(hub.id) == hub
end
end
defp fly_bypass(app_id) do
bypass = Bypass.open()
Application.put_env(:livebook, :fly_graphql_endpoint, "http://localhost:#{bypass.port}")
Bypass.expect(bypass, "POST", "/", fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
response =
case Jason.decode!(body) do
%{"variables" => %{"appId" => ^app_id}} -> fetch_app_response(app_id)
%{"variables" => %{}} -> fetch_apps_response(app_id)
end
conn
|> Plug.Conn.put_resp_content_type("application/json")
|> Plug.Conn.resp(200, Jason.encode!(response))
end)
end
defp fetch_apps_response(app_id) do
app = %{
"id" => app_id,
"organization" => %{
"id" => "l3soyvjmvtmwtl6l2drnbfuvltipprge",
"name" => "Foo Bar",
"type" => "PERSONAL"
}
}
%{"data" => %{"apps" => %{"nodes" => [app]}}}
end
defp fetch_app_response(app_id) do
app = %{
"id" => app_id,
"name" => app_id,
"hostname" => app_id <> ".fly.dev",
"platformVersion" => "nomad",
"deployed" => true,
"status" => "running"
}
%{"data" => %{"app" => app}}
end
end

View file

@ -1,5 +1,5 @@
defmodule LivebookWeb.HubLiveTest do
use LivebookWeb.ConnCase, async: true
defmodule LivebookWeb.Hub.NewLiveTest do
use LivebookWeb.ConnCase
import Phoenix.LiveViewTest
@ -7,12 +7,11 @@ defmodule LivebookWeb.HubLiveTest do
setup do
on_exit(&Hubs.clean_hubs/0)
:ok
end
test "render hub selection cards", %{conn: conn} do
{:ok, _view, html} = live(conn, "/hub")
{:ok, _view, html} = live(conn, Routes.hub_path(conn, :new))
assert html =~ "Fly"
assert html =~ "Livebook Enterprise"
@ -20,9 +19,9 @@ defmodule LivebookWeb.HubLiveTest do
describe "fly" do
test "persists fly", %{conn: conn} do
fly_app_bypass("123456789")
fly_bypass("123456789")
{:ok, view, _html} = live(conn, "/hub")
{:ok, view, _html} = live(conn, Routes.hub_path(conn, :new))
assert view
|> element("#fly")
@ -54,7 +53,7 @@ defmodule LivebookWeb.HubLiveTest do
|> render_submit(%{"fly" => attrs})
|> follow_redirect(conn)
assert render(view) =~ "Hub created successfully"
assert render(view) =~ "Hub added successfully"
assert view
|> element("#hubs")
@ -69,60 +68,11 @@ defmodule LivebookWeb.HubLiveTest do
|> render() =~ "My Foo Hub"
end
test "updates fly", %{conn: conn} do
fly_app_bypass("987654321")
fly = insert_hub(:fly, id: "fly-987654321", application_id: "987654321")
{:ok, view, _html} = live(conn, "/hub/fly-987654321")
assert render(view) =~ "2. Configure your Hub"
assert render(view) =~
~s(<option selected="selected" value="987654321">Foo Bar - 987654321</option>)
attrs = %{
"access_token" => "dummy access token",
"application_id" => "987654321",
"hub_name" => "Personal Hub",
"hub_color" => "#FF00FF"
}
view
|> element("#fly-form")
|> render_change(%{"fly" => attrs})
refute view
|> element("#fly-form .invalid-feedback")
|> has_element?()
assert {:ok, view, _html} =
view
|> element("#fly-form")
|> render_submit(%{"fly" => attrs})
|> follow_redirect(conn)
assert render(view) =~ "Hub updated successfully"
assert view
|> element("#hubs")
|> render() =~ ~s/style="color: #FF00FF"/
assert view
|> element("#hubs")
|> render() =~ "/hub/fly-987654321"
assert view
|> element("#hubs")
|> render() =~ "Personal Hub"
refute Hubs.fetch_hub!("fly-987654321") == fly
end
test "fails to create existing hub", %{conn: conn} do
fly = insert_hub(:fly, id: "fly-foo", application_id: "foo")
fly_app_bypass("foo")
hub = insert_hub(:fly, id: "fly-foo", application_id: "foo")
fly_bypass(hub.application_id)
{:ok, view, _html} = live(conn, "/hub")
{:ok, view, _html} = live(conn, Routes.hub_path(conn, :new))
assert view
|> element("#fly")
@ -154,24 +104,40 @@ defmodule LivebookWeb.HubLiveTest do
assert view
|> element("#hubs")
|> render() =~ ~s/style="color: #{fly.hub_color}"/
|> render() =~ ~s/style="color: #{hub.hub_color}"/
assert view
|> element("#hubs")
|> render() =~ "/hub/fly-foo"
|> render() =~ Routes.hub_path(conn, :edit, hub.id)
assert view
|> element("#hubs")
|> render() =~ fly.hub_name
|> render() =~ hub.hub_name
assert Hubs.fetch_hub!("fly-foo") == fly
assert Hubs.fetch_hub!(hub.id) == hub
end
end
defp fly_app_bypass(app_id) do
defp fly_bypass(app_id) do
bypass = Bypass.open()
Application.put_env(:livebook, :fly_graphql_endpoint, "http://localhost:#{bypass.port}")
Bypass.expect(bypass, "POST", "/", fn conn ->
{:ok, body, conn} = Plug.Conn.read_body(conn)
response =
case Jason.decode!(body) do
%{"variables" => %{"appId" => ^app_id}} -> fetch_app_response(app_id)
%{"variables" => %{}} -> fetch_apps_response(app_id)
end
conn
|> Plug.Conn.put_resp_content_type("application/json")
|> Plug.Conn.resp(200, Jason.encode!(response))
end)
end
defp fetch_apps_response(app_id) do
app = %{
"id" => app_id,
"organization" => %{
@ -181,12 +147,19 @@ defmodule LivebookWeb.HubLiveTest do
}
}
response = %{"data" => %{"apps" => %{"nodes" => [app]}}}
%{"data" => %{"apps" => %{"nodes" => [app]}}}
end
Bypass.expect(bypass, "POST", "/", fn conn ->
conn
|> Plug.Conn.put_resp_content_type("application/json")
|> Plug.Conn.resp(200, Jason.encode!(response))
end)
defp fetch_app_response(app_id) do
app = %{
"id" => app_id,
"name" => app_id,
"hostname" => app_id <> ".fly.dev",
"platformVersion" => "nomad",
"deployed" => true,
"status" => "running"
}
%{"data" => %{"app" => app}}
end
end