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
This commit is contained in:
Jonatan Kłosko 2022-04-04 19:48:57 +02:00 committed by GitHub
parent 9d7c795f0a
commit 5117fd6e64
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 128 additions and 67 deletions

View file

@ -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 {

View file

@ -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);

View file

@ -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()

View file

@ -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"}]}
]
}
}
]

View file

@ -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

View file

@ -446,8 +446,7 @@ defmodule LivebookWeb.LiveHelpers do
|> assign_new(:secondary_click, fn -> false end)
~H"""
<div class="relative"
id={@id}>
<div class="menu" id={@id}>
<div
phx-click={not @disabled && JS.toggle(to: "##{@id}-content")}
phx-click-away={JS.hide(to: "##{@id}-content")}
@ -456,13 +455,40 @@ defmodule LivebookWeb.LiveHelpers do
phx-key="escape">
<%= render_slot(@toggle) %>
</div>
<menu id={"#{@id}-content"} class={"hidden menu #{@position}"} role="menu">
<menu id={"#{@id}-content"} class={"hidden menu-content #{@position}"} role="menu">
<%= render_slot(@content) %>
</menu>
</div>
"""
end
@doc """
A menu item that shows a submenu on hover.
This component should be used within `menu/1` content.
## Example
<.submenu>
<button class"menu-item" role="menuitem">Submenu</button>
<:content>
<button class"menu-item" role="menuitem">Option 1</button>
<.:content>
</.submenu>
"""
def submenu(assigns) do
~H"""
<div class="submenu">
<%= render_slot(@inner_block) %>
<div class="submenu-content">
<menu class="menu-content relative mt-0">
<%= render_slot(@content) %>
</menu>
</div>
</div>
"""
end
@doc """
Creates a live region with the given role.

View file

@ -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)

View file

@ -67,11 +67,10 @@ defmodule LivebookWeb.SessionLive.InsertButtonsComponent do
</:toggle>
<:content>
<%= for definition <- Enum.sort_by(@smart_cell_definitions, & &1.name) do %>
<button class="menu-item text-gray-500"
role="menuitem"
phx-click={on_smart_cell_click(definition, @section_id, @cell_id)}>
<span class="font-medium"><%= definition.name %></span>
</button>
<.smart_cell_insert_button
definition={definition}
section_id={@section_id}
cell_id={@cell_id} />
<% end %>
</:content>
</.menu>
@ -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>
<button class="menu-item text-gray-500" role="menuitem">
<span class="font-medium"><%= @definition.name %></span>
</button>
<:content>
<%= for {variant, idx} <- Enum.with_index(@definition.requirement.variants) do %>
<button class="menu-item text-gray-500"
role="menuitem"
phx-click={on_smart_cell_click(@definition, idx, @section_id, @cell_id)}>
<span class="font-medium"><%= variant.name %></span>
</button>
<% end %>
</:content>
</.submenu>
"""
end
defp smart_cell_insert_button(assigns) do
~H"""
<button class="menu-item text-gray-500"
role="menuitem"
phx-click={on_smart_cell_click(@definition, 0, @section_id, @cell_id)}>
<span class="font-medium"><%= @definition.name %></span>
</button>
"""
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'''

View file

@ -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