Add output type setting to apps (#1905)

This commit is contained in:
Jonatan Kłosko 2023-05-11 14:07:19 +02:00 committed by GitHub
parent b4a6b76824
commit 022f395bce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 48 additions and 26 deletions

View file

@ -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(
%{},

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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