From 5117fd6e64be3606221930b4571bc609c80ce1f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonatan=20K=C5=82osko?= Date: Mon, 4 Apr 2022 19:48:57 +0200 Subject: [PATCH] List database types when adding database connection for the first time (#1090) * List database types when adding database connection for the first time * Rename classes --- assets/css/components.css | 20 +++++++- assets/js/hooks/session.js | 1 - lib/livebook/runtime.ex | 11 ++++- lib/livebook/runtime/elixir_standalone.ex | 23 ++++++++-- lib/livebook/session.ex | 18 -------- lib/livebook_web/live/live_helpers.ex | 32 +++++++++++-- lib/livebook_web/live/session_live.ex | 12 ++++- .../session_live/insert_buttons_component.ex | 46 +++++++++++++++---- test/livebook/session_test.exs | 32 ++----------- 9 files changed, 128 insertions(+), 67 deletions(-) diff --git a/assets/css/components.css b/assets/css/components.css index bfe5e1bde..3af696b73 100644 --- a/assets/css/components.css +++ b/assets/css/components.css @@ -209,15 +209,19 @@ /* Toggleable menu */ .menu { + @apply relative; + } + + .menu-content { @apply absolute z-30 rounded-lg bg-white flex flex-col py-2 mt-1; box-shadow: 0px 15px 99px rgba(13, 24, 41, 0.15); } - .menu.right { + .menu-content.right { @apply right-0; } - .menu.left { + .menu-content.left { @apply left-0; } @@ -233,6 +237,18 @@ @apply pointer-events-none opacity-50; } + .submenu { + @apply relative; + } + + .submenu:not(:hover):not(:focus-within) .submenu-content { + @apply hidden; + } + + .submenu-content { + @apply absolute -top-2 right-0 translate-x-full pl-2; + } + /* Boxes */ .error-box { diff --git a/assets/js/hooks/session.js b/assets/js/hooks/session.js index a5ece8a33..86b469bf0 100644 --- a/assets/js/hooks/session.js +++ b/assets/js/hooks/session.js @@ -79,7 +79,6 @@ const Session = { // DOM events - this._handleDocumentKeyDown = this.handleDocumentKeyDown.bind(this); this._handleDocumentKeyDown = this.handleDocumentKeyDown.bind(this); this._handleDocumentMouseDown = this.handleDocumentMouseDown.bind(this); this._handleDocumentFocus = this.handleDocumentFocus.bind(this); diff --git a/lib/livebook/runtime.ex b/lib/livebook/runtime.ex index 5ef2044cf..860457b71 100644 --- a/lib/livebook/runtime.ex +++ b/lib/livebook/runtime.ex @@ -195,7 +195,16 @@ defprotocol Livebook.Runtime do @type smart_cell_definition :: %{ kind: String.t(), name: String.t(), - requirement: nil | %{name: String.t(), dependencies: list(dependency())} + requirement: nil | smart_cell_requirement() + } + + @type smart_cell_requirement :: %{ + name: String.t(), + variants: + list(%{ + name: String.t(), + dependencies: list(dependency()) + }) } @type dependency :: term() diff --git a/lib/livebook/runtime/elixir_standalone.ex b/lib/livebook/runtime/elixir_standalone.ex index eaea289ca..3a7678d6b 100644 --- a/lib/livebook/runtime/elixir_standalone.ex +++ b/lib/livebook/runtime/elixir_standalone.ex @@ -18,23 +18,38 @@ defmodule Livebook.Runtime.ElixirStandalone do } kino_dep = {:kino, github: "livebook-dev/kino"} - vega_lite_dep = {:vega_lite, "~> 0.1.3"} @extra_smart_cell_definitions [ %{ kind: "Elixir.Kino.SmartCell.DBConnection", name: "Database connection", - requirement: %{name: "Kino", dependencies: [kino_dep]} + requirement: %{ + name: "Kino", + variants: [ + %{name: "PostgreSQL", dependencies: [kino_dep, {:postgrex, "~> 0.16.1"}]}, + %{name: "MySQL", dependencies: [kino_dep, {:myxql, "~> 0.6.1"}]} + ] + } }, %{ kind: "Elixir.Kino.SmartCell.SQL", name: "SQL query", - requirement: %{name: "Kino", dependencies: [kino_dep]} + requirement: %{ + name: "Kino", + variants: [ + %{name: "Default", dependencies: [kino_dep]} + ] + } }, %{ kind: "Elixir.Kino.SmartCell.ChartBuilder", name: "Chart builder", - requirement: %{name: "Kino", dependencies: [kino_dep, vega_lite_dep]} + requirement: %{ + name: "Kino", + variants: [ + %{name: "Default", dependencies: [kino_dep, {:vega_lite, "~> 0.1.3"}]} + ] + } } ] diff --git a/lib/livebook/session.ex b/lib/livebook/session.ex index c7ae9d258..e1045cf20 100644 --- a/lib/livebook/session.ex +++ b/lib/livebook/session.ex @@ -310,14 +310,6 @@ defmodule Livebook.Session do GenServer.cast(pid, {:convert_smart_cell, self(), cell_id}) end - @doc """ - Sends smart cell dependencies addition request to the server. - """ - @spec add_smart_cell_dependencies(pid(), String.t()) :: :ok - def add_smart_cell_dependencies(pid, kind) do - GenServer.cast(pid, {:add_smart_cell_dependencies, kind}) - end - @doc """ Sends dependencies addition request to the server. """ @@ -747,16 +739,6 @@ defmodule Livebook.Session do {:noreply, state} end - def handle_cast({:add_smart_cell_dependencies, kind}, state) do - state = - case Enum.find(state.data.smart_cell_definitions, &(&1.kind == kind)) do - %{requirement: %{dependencies: dependencies}} -> do_add_dependencies(state, dependencies) - _ -> state - end - - {:noreply, state} - end - def handle_cast({:add_dependencies, dependencies}, state) do {:noreply, do_add_dependencies(state, dependencies)} end diff --git a/lib/livebook_web/live/live_helpers.ex b/lib/livebook_web/live/live_helpers.ex index 4eabff01a..cf2d1be0c 100644 --- a/lib/livebook_web/live/live_helpers.ex +++ b/lib/livebook_web/live/live_helpers.ex @@ -446,8 +446,7 @@ defmodule LivebookWeb.LiveHelpers do |> assign_new(:secondary_click, fn -> false end) ~H""" -
+ """ end + @doc """ + A menu item that shows a submenu on hover. + + This component should be used within `menu/1` content. + + ## Example + + <.submenu> + + <:content> + + <.:content> + + """ + def submenu(assigns) do + ~H""" + + """ + end + @doc """ Creates a live region with the given role. diff --git a/lib/livebook_web/live/session_live.ex b/lib/livebook_web/live/session_live.ex index 6256c5cc8..395c37529 100644 --- a/lib/livebook_web/live/session_live.ex +++ b/lib/livebook_web/live/session_live.ex @@ -723,8 +723,16 @@ defmodule LivebookWeb.SessionLive do {:noreply, socket} end - def handle_event("add_smart_cell_dependencies", %{"kind" => kind}, socket) do - Session.add_smart_cell_dependencies(socket.assigns.session.pid, kind) + def handle_event( + "add_smart_cell_dependencies", + %{"kind" => kind, "variant_idx" => variant_idx}, + socket + ) do + with %{requirement: %{variants: variants}} <- + Enum.find(socket.private.data.smart_cell_definitions, &(&1.kind == kind)), + {:ok, %{dependencies: dependencies}} <- Enum.fetch(variants, variant_idx) do + Session.add_dependencies(socket.assigns.session.pid, dependencies) + end {status, socket} = maybe_reconnect_runtime(socket) diff --git a/lib/livebook_web/live/session_live/insert_buttons_component.ex b/lib/livebook_web/live/session_live/insert_buttons_component.ex index f2ace4a87..0045b6d33 100644 --- a/lib/livebook_web/live/session_live/insert_buttons_component.ex +++ b/lib/livebook_web/live/session_live/insert_buttons_component.ex @@ -67,11 +67,10 @@ defmodule LivebookWeb.SessionLive.InsertButtonsComponent do <:content> <%= for definition <- Enum.sort_by(@smart_cell_definitions, & &1.name) do %> - + <.smart_cell_insert_button + definition={definition} + section_id={@section_id} + cell_id={@cell_id} /> <% end %> @@ -81,13 +80,44 @@ defmodule LivebookWeb.SessionLive.InsertButtonsComponent do """ end - defp on_smart_cell_click(%{requirement: nil} = definition, section_id, cell_id) do + defp smart_cell_insert_button(%{definition: %{requirement: %{variants: [_, _ | _]}}} = assigns) do + ~H""" + <.submenu> + + <:content> + <%= for {variant, idx} <- Enum.with_index(@definition.requirement.variants) do %> + + <% end %> + + + """ + end + + defp smart_cell_insert_button(assigns) do + ~H""" + + """ + end + + defp on_smart_cell_click(%{requirement: nil} = definition, _variant_idx, section_id, cell_id) do insert_smart_cell(definition, section_id, cell_id) end - defp on_smart_cell_click(%{requirement: %{}} = definition, section_id, cell_id) do + defp on_smart_cell_click(%{requirement: %{}} = definition, variant_idx, section_id, cell_id) do with_confirm( - JS.push("add_smart_cell_dependencies", value: %{kind: definition.kind}) + JS.push("add_smart_cell_dependencies", + value: %{kind: definition.kind, variant_idx: variant_idx} + ) |> insert_smart_cell(definition, section_id, cell_id), title: "Add package", description: ~s''' diff --git a/test/livebook/session_test.exs b/test/livebook/session_test.exs index f5c808843..027d95b98 100644 --- a/test/livebook/session_test.exs +++ b/test/livebook/session_test.exs @@ -134,27 +134,15 @@ defmodule Livebook.SessionTest do end end - describe "add_smart_cell_dependencies/2" do - test "applies source change to the setup cell to include the smart cell dependency", + describe "add_dependencies/2" do + test "applies source change to the setup cell to include the given dependencies", %{session: session} do runtime = connected_noop_runtime() Session.set_runtime(session.pid, runtime) - send( - session.pid, - {:runtime_smart_cell_definitions, - [ - %{ - kind: "text", - name: "Text", - requirement: %{name: "Kino", dependencies: [{:kino, "~> 0.5.0"}]} - } - ]} - ) - Phoenix.PubSub.subscribe(Livebook.PubSub, "sessions:#{session.id}") - Session.add_smart_cell_dependencies(session.pid, "text") + Session.add_dependencies(session.pid, [{:kino, "~> 0.5.0"}]) session_pid = session.pid assert_receive {:operation, {:apply_cell_delta, ^session_pid, "setup", :primary, _delta, 1}} @@ -183,21 +171,9 @@ defmodule Livebook.SessionTest do runtime = connected_noop_runtime() Session.set_runtime(session.pid, runtime) - send( - session.pid, - {:runtime_smart_cell_definitions, - [ - %{ - kind: "text", - name: "Text", - requirement: %{name: "Kino", dependencies: [{:kino, "~> 0.5.0"}]} - } - ]} - ) - Phoenix.PubSub.subscribe(Livebook.PubSub, "sessions:#{session.id}") - Session.add_smart_cell_dependencies(session.pid, "text") + Session.add_dependencies(session.pid, [{:kino, "~> 0.5.0"}]) assert_receive {:error, "failed to add dependencies to the setup cell, reason:" <> _} end