From 2cbd81eaf5c24f64108a06c3f8adf7258ddc1daa Mon Sep 17 00:00:00 2001 From: Alexandre de Souza Date: Tue, 30 Aug 2022 11:32:48 -0300 Subject: [PATCH] Migrate HubLive to Phoenix format (#1373) --- lib/livebook/hubs/fly.ex | 10 +- lib/livebook/hubs/fly_client.ex | 23 ++- .../live/hub/edit/fly_component.ex | 134 ++++++++++++++++++ lib/livebook_web/live/hub/edit_live.ex | 44 ++++++ .../{hub_live => hub/new}/fly_component.ex | 112 ++++----------- .../live/{hub_live.ex => hub/new_live.ex} | 64 ++------- lib/livebook_web/router.ex | 4 +- test/livebook/hubs/fly_client_test.exs | 40 +++++- test/livebook_web/live/hub/edit_live_test.exs | 104 ++++++++++++++ .../new_live_test.exs} | 113 ++++++--------- 10 files changed, 433 insertions(+), 215 deletions(-) create mode 100644 lib/livebook_web/live/hub/edit/fly_component.ex create mode 100644 lib/livebook_web/live/hub/edit_live.ex rename lib/livebook_web/live/{hub_live => hub/new}/fly_component.ex (64%) rename lib/livebook_web/live/{hub_live.ex => hub/new_live.ex} (62%) create mode 100644 test/livebook_web/live/hub/edit_live_test.exs rename test/livebook_web/live/{hub_live_test.exs => hub/new_live_test.exs} (60%) diff --git a/lib/livebook/hubs/fly.ex b/lib/livebook/hubs/fly.ex index 01d8b0f4a..da084a868 100644 --- a/lib/livebook/hubs/fly.ex +++ b/lib/livebook/hubs/fly.ex @@ -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 diff --git a/lib/livebook/hubs/fly_client.ex b/lib/livebook/hubs/fly_client.ex index 8806a8a3a..f555d1d30 100644 --- a/lib/livebook/hubs/fly_client.ex +++ b/lib/livebook/hubs/fly_client.ex @@ -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} -> diff --git a/lib/livebook_web/live/hub/edit/fly_component.ex b/lib/livebook_web/live/hub/edit/fly_component.ex new file mode 100644 index 000000000..760965d53 --- /dev/null +++ b/lib/livebook_web/live/hub/edit/fly_component.ex @@ -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""" +
+ +
+
+
+ <.labeled_text label="Application ID"> + <%= @hub.application_id %> + + <.labeled_text label="Type"> + Fly + +
+ + + <.remix_icon icon="dashboard-2-line" class="align-middle mr-1" /> + See app on Fly + +
+
+ +
+

+ General +

+ + <.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" + > +
+
+

+ Name +

+ <%= text_input(f, :hub_name, class: "input") %> + <%= error_tag(f, :hub_name) %> +
+ +
+

+ Color +

+ +
+
+
+
+
+ <%= text_input(f, :hub_color, + class: "input", + spellcheck: "false", + maxlength: 7 + ) %> + + <%= error_tag(f, :hub_color) %> +
+
+
+
+ + <%= submit("Update Hub", + class: "button-base button-blue", + phx_disable_with: "Updating...", + disabled: not @changeset.valid? + ) %> + +
+
+ """ + 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 diff --git a/lib/livebook_web/live/hub/edit_live.ex b/lib/livebook_web/live/hub/edit_live.ex new file mode 100644 index 000000000..44529715e --- /dev/null +++ b/lib/livebook_web/live/hub/edit_live.ex @@ -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""" + +
+
+ +
+ + <%= if @type == "fly" do %> + <.live_component module={LivebookWeb.Hub.Edit.FlyComponent} hub={@hub} id="fly-form" /> + <% end %> +
+
+ """ + end +end diff --git a/lib/livebook_web/live/hub_live/fly_component.ex b/lib/livebook_web/live/hub/new/fly_component.ex similarity index 64% rename from lib/livebook_web/live/hub_live/fly_component.ex rename to lib/livebook_web/live/hub/new/fly_component.ex index 93457bb62..2a9e9e8e6 100644 --- a/lib/livebook_web/live/hub_live/fly_component.ex +++ b/lib/livebook_web/live/hub/new/fly_component.ex @@ -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

Application

- <%= select(f, :application_id, @select_options, - class: "input", - disabled: @operation == :edit - ) %> + <%= select(f, :application_id, @select_options, class: "input") %> <%= error_tag(f, :application_id) %>
@@ -99,10 +100,10 @@ defmodule LivebookWeb.HubLive.FlyComponent do - <%= 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 %> @@ -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 diff --git a/lib/livebook_web/live/hub_live.ex b/lib/livebook_web/live/hub/new_live.ex similarity index 62% rename from lib/livebook_web/live/hub_live.ex rename to lib/livebook_web/live/hub/new_live.ex index b5a3b4d70..0492fb97d 100644 --- a/lib/livebook_web/live/hub_live.ex +++ b/lib/livebook_web/live/hub/new_live.ex @@ -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"""
- +

Manage your Livebooks in the cloud with Hubs.

@@ -44,7 +34,7 @@ defmodule LivebookWeb.HubLive do
- <.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")) %> @@ -53,7 +43,7 @@ defmodule LivebookWeb.HubLive do - <.card_item id="enterprise" selected={@selected_provider} title="Livebook Enterprise"> + <.card_item id="enterprise" selected={@selected_type} title="Livebook Enterprise"> <:logo>
- <%= if @selected_provider do %> + <%= if @selected_type do %>

2. Configure your Hub

- <%= 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 %>
Livebook Enterprise is currently in closed beta. If you want to learn more, card_item_bg_color(@id, @selected)} - phx-click={JS.push("select_provider", value: %{value: @id})} + phx-click={JS.push("select_type", value: %{value: @id})} >