mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-12-11 14:16:44 +08:00
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:
parent
9d7c795f0a
commit
5117fd6e64
9 changed files with 128 additions and 67 deletions
|
|
@ -209,15 +209,19 @@
|
||||||
/* Toggleable menu */
|
/* Toggleable menu */
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
|
@apply relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-content {
|
||||||
@apply absolute z-30 rounded-lg bg-white flex flex-col py-2 mt-1;
|
@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);
|
box-shadow: 0px 15px 99px rgba(13, 24, 41, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu.right {
|
.menu-content.right {
|
||||||
@apply right-0;
|
@apply right-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu.left {
|
.menu-content.left {
|
||||||
@apply left-0;
|
@apply left-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -233,6 +237,18 @@
|
||||||
@apply pointer-events-none opacity-50;
|
@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 */
|
/* Boxes */
|
||||||
|
|
||||||
.error-box {
|
.error-box {
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,6 @@ const Session = {
|
||||||
|
|
||||||
// DOM events
|
// DOM events
|
||||||
|
|
||||||
this._handleDocumentKeyDown = this.handleDocumentKeyDown.bind(this);
|
|
||||||
this._handleDocumentKeyDown = this.handleDocumentKeyDown.bind(this);
|
this._handleDocumentKeyDown = this.handleDocumentKeyDown.bind(this);
|
||||||
this._handleDocumentMouseDown = this.handleDocumentMouseDown.bind(this);
|
this._handleDocumentMouseDown = this.handleDocumentMouseDown.bind(this);
|
||||||
this._handleDocumentFocus = this.handleDocumentFocus.bind(this);
|
this._handleDocumentFocus = this.handleDocumentFocus.bind(this);
|
||||||
|
|
|
||||||
|
|
@ -195,7 +195,16 @@ defprotocol Livebook.Runtime do
|
||||||
@type smart_cell_definition :: %{
|
@type smart_cell_definition :: %{
|
||||||
kind: String.t(),
|
kind: String.t(),
|
||||||
name: 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()
|
@type dependency :: term()
|
||||||
|
|
|
||||||
|
|
@ -18,23 +18,38 @@ defmodule Livebook.Runtime.ElixirStandalone do
|
||||||
}
|
}
|
||||||
|
|
||||||
kino_dep = {:kino, github: "livebook-dev/kino"}
|
kino_dep = {:kino, github: "livebook-dev/kino"}
|
||||||
vega_lite_dep = {:vega_lite, "~> 0.1.3"}
|
|
||||||
|
|
||||||
@extra_smart_cell_definitions [
|
@extra_smart_cell_definitions [
|
||||||
%{
|
%{
|
||||||
kind: "Elixir.Kino.SmartCell.DBConnection",
|
kind: "Elixir.Kino.SmartCell.DBConnection",
|
||||||
name: "Database connection",
|
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",
|
kind: "Elixir.Kino.SmartCell.SQL",
|
||||||
name: "SQL query",
|
name: "SQL query",
|
||||||
requirement: %{name: "Kino", dependencies: [kino_dep]}
|
requirement: %{
|
||||||
|
name: "Kino",
|
||||||
|
variants: [
|
||||||
|
%{name: "Default", dependencies: [kino_dep]}
|
||||||
|
]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
kind: "Elixir.Kino.SmartCell.ChartBuilder",
|
kind: "Elixir.Kino.SmartCell.ChartBuilder",
|
||||||
name: "Chart builder",
|
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"}]}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -310,14 +310,6 @@ defmodule Livebook.Session do
|
||||||
GenServer.cast(pid, {:convert_smart_cell, self(), cell_id})
|
GenServer.cast(pid, {:convert_smart_cell, self(), cell_id})
|
||||||
end
|
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 """
|
@doc """
|
||||||
Sends dependencies addition request to the server.
|
Sends dependencies addition request to the server.
|
||||||
"""
|
"""
|
||||||
|
|
@ -747,16 +739,6 @@ defmodule Livebook.Session do
|
||||||
{:noreply, state}
|
{:noreply, state}
|
||||||
end
|
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
|
def handle_cast({:add_dependencies, dependencies}, state) do
|
||||||
{:noreply, do_add_dependencies(state, dependencies)}
|
{:noreply, do_add_dependencies(state, dependencies)}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -446,8 +446,7 @@ defmodule LivebookWeb.LiveHelpers do
|
||||||
|> assign_new(:secondary_click, fn -> false end)
|
|> assign_new(:secondary_click, fn -> false end)
|
||||||
|
|
||||||
~H"""
|
~H"""
|
||||||
<div class="relative"
|
<div class="menu" id={@id}>
|
||||||
id={@id}>
|
|
||||||
<div
|
<div
|
||||||
phx-click={not @disabled && JS.toggle(to: "##{@id}-content")}
|
phx-click={not @disabled && JS.toggle(to: "##{@id}-content")}
|
||||||
phx-click-away={JS.hide(to: "##{@id}-content")}
|
phx-click-away={JS.hide(to: "##{@id}-content")}
|
||||||
|
|
@ -456,13 +455,40 @@ defmodule LivebookWeb.LiveHelpers do
|
||||||
phx-key="escape">
|
phx-key="escape">
|
||||||
<%= render_slot(@toggle) %>
|
<%= render_slot(@toggle) %>
|
||||||
</div>
|
</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) %>
|
<%= render_slot(@content) %>
|
||||||
</menu>
|
</menu>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
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 """
|
@doc """
|
||||||
Creates a live region with the given role.
|
Creates a live region with the given role.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -723,8 +723,16 @@ defmodule LivebookWeb.SessionLive do
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("add_smart_cell_dependencies", %{"kind" => kind}, socket) do
|
def handle_event(
|
||||||
Session.add_smart_cell_dependencies(socket.assigns.session.pid, kind)
|
"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)
|
{status, socket} = maybe_reconnect_runtime(socket)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,11 +67,10 @@ defmodule LivebookWeb.SessionLive.InsertButtonsComponent do
|
||||||
</:toggle>
|
</:toggle>
|
||||||
<:content>
|
<:content>
|
||||||
<%= for definition <- Enum.sort_by(@smart_cell_definitions, & &1.name) do %>
|
<%= for definition <- Enum.sort_by(@smart_cell_definitions, & &1.name) do %>
|
||||||
<button class="menu-item text-gray-500"
|
<.smart_cell_insert_button
|
||||||
role="menuitem"
|
definition={definition}
|
||||||
phx-click={on_smart_cell_click(definition, @section_id, @cell_id)}>
|
section_id={@section_id}
|
||||||
<span class="font-medium"><%= definition.name %></span>
|
cell_id={@cell_id} />
|
||||||
</button>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</:content>
|
</:content>
|
||||||
</.menu>
|
</.menu>
|
||||||
|
|
@ -81,13 +80,44 @@ defmodule LivebookWeb.SessionLive.InsertButtonsComponent do
|
||||||
"""
|
"""
|
||||||
end
|
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)
|
insert_smart_cell(definition, section_id, cell_id)
|
||||||
end
|
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(
|
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),
|
|> insert_smart_cell(definition, section_id, cell_id),
|
||||||
title: "Add package",
|
title: "Add package",
|
||||||
description: ~s'''
|
description: ~s'''
|
||||||
|
|
|
||||||
|
|
@ -134,27 +134,15 @@ defmodule Livebook.SessionTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "add_smart_cell_dependencies/2" do
|
describe "add_dependencies/2" do
|
||||||
test "applies source change to the setup cell to include the smart cell dependency",
|
test "applies source change to the setup cell to include the given dependencies",
|
||||||
%{session: session} do
|
%{session: session} do
|
||||||
runtime = connected_noop_runtime()
|
runtime = connected_noop_runtime()
|
||||||
Session.set_runtime(session.pid, 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}")
|
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
|
session_pid = session.pid
|
||||||
assert_receive {:operation, {:apply_cell_delta, ^session_pid, "setup", :primary, _delta, 1}}
|
assert_receive {:operation, {:apply_cell_delta, ^session_pid, "setup", :primary, _delta, 1}}
|
||||||
|
|
@ -183,21 +171,9 @@ defmodule Livebook.SessionTest do
|
||||||
runtime = connected_noop_runtime()
|
runtime = connected_noop_runtime()
|
||||||
Session.set_runtime(session.pid, 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}")
|
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:" <> _}
|
assert_receive {:error, "failed to add dependencies to the setup cell, reason:" <> _}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue