mirror of
https://github.com/livebook-dev/livebook.git
synced 2024-09-20 10:05:57 +08:00
Add output type setting to apps (#1905)
This commit is contained in:
parent
b4a6b76824
commit
022f395bce
|
@ -86,7 +86,7 @@ defmodule Livebook.LiveMarkdown.Export do
|
|||
end
|
||||
|
||||
defp app_settings_metadata(app_settings) do
|
||||
keys = [:slug, :access_type, :show_source]
|
||||
keys = [:slug, :access_type, :show_source, :output_type]
|
||||
|
||||
put_unless_default(
|
||||
%{},
|
||||
|
|
|
@ -410,6 +410,9 @@ defmodule Livebook.LiveMarkdown.Import do
|
|||
{"access_type", access_type}, attrs when access_type in ["public", "protected"] ->
|
||||
Map.put(attrs, :access_type, String.to_atom(access_type))
|
||||
|
||||
{"output_type", output_type}, attrs when output_type in ["all", "rich"] ->
|
||||
Map.put(attrs, :output_type, String.to_atom(output_type))
|
||||
|
||||
_entry, attrs ->
|
||||
attrs
|
||||
end)
|
||||
|
|
|
@ -9,7 +9,8 @@ defmodule Livebook.Notebook.AppSettings do
|
|||
slug: String.t() | nil,
|
||||
access_type: access_type(),
|
||||
password: String.t() | nil,
|
||||
show_source: boolean()
|
||||
show_source: boolean(),
|
||||
output_type: :all | :rich
|
||||
}
|
||||
|
||||
@type access_type :: :public | :protected
|
||||
|
@ -20,6 +21,7 @@ defmodule Livebook.Notebook.AppSettings do
|
|||
field :access_type, Ecto.Enum, values: [:public, :protected]
|
||||
field :password, :string
|
||||
field :show_source, :boolean
|
||||
field :output_type, Ecto.Enum, values: [:all, :rich]
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -31,7 +33,8 @@ defmodule Livebook.Notebook.AppSettings do
|
|||
slug: nil,
|
||||
access_type: :protected,
|
||||
password: generate_password(),
|
||||
show_source: false
|
||||
show_source: false,
|
||||
output_type: :all
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -58,8 +61,8 @@ defmodule Livebook.Notebook.AppSettings do
|
|||
|
||||
defp changeset(settings, attrs) do
|
||||
settings
|
||||
|> cast(attrs, [:slug, :access_type, :show_source])
|
||||
|> validate_required([:slug, :access_type, :show_source])
|
||||
|> cast(attrs, [:slug, :access_type, :show_source, :output_type])
|
||||
|> validate_required([:slug, :access_type, :show_source, :output_type])
|
||||
|> validate_format(:slug, ~r/^[a-zA-Z0-9-]+$/,
|
||||
message: "slug can only contain alphanumeric characters and dashes"
|
||||
)
|
||||
|
|
|
@ -276,6 +276,7 @@ defmodule LivebookWeb.FormComponents do
|
|||
|
||||
~H"""
|
||||
<div phx-feedback-for={@name} class={[@errors != [] && "show-errors"]}>
|
||||
<.label :if={@label} for={@id}><%= @label %></.label>
|
||||
<div class="flex gap-4 text-gray-600">
|
||||
<label :for={{value, description} <- @options} class="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
|
|
|
@ -119,10 +119,6 @@ defmodule LivebookWeb.AppLive do
|
|||
input_values={output_view.input_values}
|
||||
/>
|
||||
</div>
|
||||
<div :if={@data_view.output_views == []} class="info-box">
|
||||
This deployed notebook is empty. Deployed apps only render Kino outputs.
|
||||
Ensure you use Kino for interactive visualizations and dynamic content.
|
||||
</div>
|
||||
</div>
|
||||
<div style="height: 80vh"></div>
|
||||
</div>
|
||||
|
@ -241,16 +237,19 @@ defmodule LivebookWeb.AppLive do
|
|||
for section <- Enum.reverse(notebook.sections),
|
||||
cell <- Enum.reverse(section.cells),
|
||||
Cell.evaluable?(cell),
|
||||
output <- filter_outputs(cell.outputs),
|
||||
output <- filter_outputs(cell.outputs, notebook.app_settings.output_type),
|
||||
do: output
|
||||
end
|
||||
|
||||
defp filter_outputs(outputs) do
|
||||
defp filter_outputs(outputs, :all), do: outputs
|
||||
defp filter_outputs(outputs, :rich), do: rich_outputs(outputs)
|
||||
|
||||
defp rich_outputs(outputs) do
|
||||
for output <- outputs, output = filter_output(output), do: output
|
||||
end
|
||||
|
||||
defp filter_output({idx, output})
|
||||
when elem(output, 0) in [:plain_text, :markdown, :image, :js, :control],
|
||||
when elem(output, 0) in [:plain_text, :markdown, :image, :js, :control, :input],
|
||||
do: {idx, output}
|
||||
|
||||
defp filter_output({idx, {:tabs, outputs, metadata}}) do
|
||||
|
@ -265,7 +264,7 @@ defmodule LivebookWeb.AppLive do
|
|||
end
|
||||
|
||||
defp filter_output({idx, {:grid, outputs, metadata}}) do
|
||||
outputs = filter_outputs(outputs)
|
||||
outputs = rich_outputs(outputs)
|
||||
|
||||
if outputs != [] do
|
||||
{idx, {:grid, outputs, metadata}}
|
||||
|
@ -273,7 +272,7 @@ defmodule LivebookWeb.AppLive do
|
|||
end
|
||||
|
||||
defp filter_output({idx, {:frame, outputs, metadata}}) do
|
||||
outputs = filter_outputs(outputs)
|
||||
outputs = rich_outputs(outputs)
|
||||
{idx, {:frame, outputs, metadata}}
|
||||
end
|
||||
|
||||
|
|
|
@ -198,6 +198,11 @@ defmodule LivebookWeb.SessionLive.AppInfoComponent do
|
|||
<% end %>
|
||||
</div>
|
||||
<.checkbox_field field={f[:show_source]} label="Show source" />
|
||||
<.radio_field
|
||||
field={f[:output_type]}
|
||||
label="Output type"
|
||||
options={[{"all", "All"}, {"rich", "Rich only"}]}
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-6 flex space-x-2">
|
||||
<button class="button-base button-blue" type="submit" disabled={not @changeset.valid?}>
|
||||
|
|
|
@ -119,7 +119,6 @@ defmodule LivebookWeb.SessionLive.SecretsComponent do
|
|||
/>
|
||||
<.radio_field
|
||||
field={f[:hub_id]}
|
||||
label="Storage"
|
||||
options={[
|
||||
{"", "only this session"},
|
||||
{@hub.id, "in #{@hub.hub_emoji} #{@hub.hub_name}"}
|
||||
|
|
|
@ -1150,12 +1150,13 @@ defmodule Livebook.LiveMarkdown.ExportTest do
|
|||
Notebook.AppSettings.new()
|
||||
| slug: "app",
|
||||
access_type: :public,
|
||||
show_source: true
|
||||
show_source: true,
|
||||
output_type: :rich
|
||||
}
|
||||
}
|
||||
|
||||
expected_document = """
|
||||
<!-- livebook:{"app_settings":{"access_type":"public","show_source":true,"slug":"app"}} -->
|
||||
<!-- livebook:{"app_settings":{"access_type":"public","output_type":"rich","show_source":true,"slug":"app"}} -->
|
||||
|
||||
# My Notebook
|
||||
"""
|
||||
|
|
|
@ -750,7 +750,7 @@ defmodule Livebook.LiveMarkdown.ImportTest do
|
|||
describe "app settings" do
|
||||
test "imports settings" do
|
||||
markdown = """
|
||||
<!-- livebook:{"app_settings":{"access_type":"public","show_source":true,"slug":"app"}} -->
|
||||
<!-- livebook:{"app_settings":{"access_type":"public","output_type":"rich","show_source":true,"slug":"app"}} -->
|
||||
|
||||
# My Notebook
|
||||
"""
|
||||
|
@ -759,7 +759,12 @@ defmodule Livebook.LiveMarkdown.ImportTest do
|
|||
|
||||
assert %Notebook{
|
||||
name: "My Notebook",
|
||||
app_settings: %{slug: "app", access_type: :public, show_source: true}
|
||||
app_settings: %{
|
||||
slug: "app",
|
||||
access_type: :public,
|
||||
show_source: true,
|
||||
output_type: :rich
|
||||
}
|
||||
} = notebook
|
||||
end
|
||||
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
defmodule LivebookWeb.AppLiveTest do
|
||||
use LivebookWeb.ConnCase, async: true
|
||||
|
||||
import Livebook.SessionHelpers
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
alias Livebook.Session
|
||||
|
||||
test "render guidance when Kino output is empty", %{conn: conn} do
|
||||
test "renders only rich output when output type is rich", %{conn: conn} do
|
||||
session = start_session()
|
||||
|
||||
Session.subscribe(session.id)
|
||||
|
||||
slug = Livebook.Utils.random_short_id()
|
||||
app_settings = %{Livebook.Notebook.AppSettings.new() | slug: slug}
|
||||
app_settings = %{Livebook.Notebook.AppSettings.new() | slug: slug, output_type: :rich}
|
||||
Session.set_app_settings(session.pid, app_settings)
|
||||
|
||||
Session.set_notebook_name(session.pid, "My app #{slug}")
|
||||
section_id = insert_section(session.pid)
|
||||
insert_cell_with_output(session.pid, section_id, {:stdout, "Printed output"})
|
||||
insert_cell_with_output(session.pid, section_id, {:plain_text, "Custom text"})
|
||||
|
||||
Session.deploy_app(session.pid)
|
||||
|
||||
assert_receive {:operation, {:add_app, _, _, _}}
|
||||
|
@ -22,8 +26,8 @@ defmodule LivebookWeb.AppLiveTest do
|
|||
|
||||
{:ok, view, _} = live(conn, ~p"/apps/#{slug}")
|
||||
|
||||
assert render(view) =~
|
||||
"This deployed notebook is empty. Deployed apps only render Kino outputs."
|
||||
refute render(view) =~ "Printed output"
|
||||
assert render(view) =~ "Custom text"
|
||||
end
|
||||
|
||||
defp start_session() do
|
||||
|
|
|
@ -44,13 +44,15 @@ defmodule Livebook.SessionHelpers do
|
|||
|
||||
def insert_section(session_pid) do
|
||||
Session.insert_section(session_pid, 0)
|
||||
%{notebook: %{sections: [section]}} = Session.get_data(session_pid)
|
||||
%{notebook: %{sections: [section | _]}} = Session.get_data(session_pid)
|
||||
section.id
|
||||
end
|
||||
|
||||
def insert_text_cell(session_pid, section_id, type, content \\ " ") do
|
||||
Session.insert_cell(session_pid, section_id, 0, type, %{source: content})
|
||||
%{notebook: %{sections: [%{cells: [cell]}]}} = Session.get_data(session_pid)
|
||||
data = Session.get_data(session_pid)
|
||||
{:ok, section} = Livebook.Notebook.fetch_section(data.notebook, section_id)
|
||||
cell = hd(section.cells)
|
||||
cell.id
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue