mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-10-09 21:16:26 +08:00
Add action for clearing evaluation and outputs (#661)
* Move notebook export menu item * Add action for clearing evaluation and outputs * Test data operation * Update wording * Update wording * Reorder menu items
This commit is contained in:
parent
8a0d218cbe
commit
ac1a4a5ffb
6 changed files with 128 additions and 6 deletions
|
@ -199,6 +199,18 @@ defmodule Livebook.Notebook do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Updates all cells with the given function.
|
||||||
|
"""
|
||||||
|
@spec update_cells(t(), (Cell.t() -> Cell.t())) :: t()
|
||||||
|
def update_cells(notebook, fun) do
|
||||||
|
update_in(
|
||||||
|
notebook,
|
||||||
|
[Access.key(:sections), Access.all(), Access.key(:cells), Access.all()],
|
||||||
|
fun
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Updates section with the given function.
|
Updates section with the given function.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -247,6 +247,14 @@ defmodule Livebook.Session do
|
||||||
GenServer.cast(pid, {:cancel_cell_evaluation, self(), cell_id})
|
GenServer.cast(pid, {:cancel_cell_evaluation, self(), cell_id})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Asynchronously sends erase outputs request to the server.
|
||||||
|
"""
|
||||||
|
@spec erase_outputs(pid()) :: :ok
|
||||||
|
def erase_outputs(pid) do
|
||||||
|
GenServer.cast(pid, {:erase_outputs, self()})
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Asynchronously sends notebook name update request to the server.
|
Asynchronously sends notebook name update request to the server.
|
||||||
"""
|
"""
|
||||||
|
@ -520,6 +528,11 @@ defmodule Livebook.Session do
|
||||||
{:noreply, handle_operation(state, operation)}
|
{:noreply, handle_operation(state, operation)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_cast({:erase_outputs, client_pid}, state) do
|
||||||
|
operation = {:erase_outputs, client_pid}
|
||||||
|
{:noreply, handle_operation(state, operation)}
|
||||||
|
end
|
||||||
|
|
||||||
def handle_cast({:set_notebook_name, client_pid, name}, state) do
|
def handle_cast({:set_notebook_name, client_pid, name}, state) do
|
||||||
operation = {:set_notebook_name, client_pid, name}
|
operation = {:set_notebook_name, client_pid, name}
|
||||||
{:noreply, handle_operation(state, operation)}
|
{:noreply, handle_operation(state, operation)}
|
||||||
|
|
|
@ -129,6 +129,7 @@ defmodule Livebook.Session.Data do
|
||||||
| {:reflect_main_evaluation_failure, pid()}
|
| {:reflect_main_evaluation_failure, pid()}
|
||||||
| {:reflect_evaluation_failure, pid(), Section.id()}
|
| {:reflect_evaluation_failure, pid(), Section.id()}
|
||||||
| {:cancel_cell_evaluation, pid(), Cell.id()}
|
| {:cancel_cell_evaluation, pid(), Cell.id()}
|
||||||
|
| {:erase_outputs, pid()}
|
||||||
| {:set_notebook_name, pid(), String.t()}
|
| {:set_notebook_name, pid(), String.t()}
|
||||||
| {:set_section_name, pid(), Section.id(), String.t()}
|
| {:set_section_name, pid(), Section.id(), String.t()}
|
||||||
| {:client_join, pid(), User.t()}
|
| {:client_join, pid(), User.t()}
|
||||||
|
@ -459,6 +460,13 @@ defmodule Livebook.Session.Data do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def apply_operation(data, {:erase_outputs, _client_pid}) do
|
||||||
|
data
|
||||||
|
|> with_actions()
|
||||||
|
|> erase_outputs()
|
||||||
|
|> wrap_ok()
|
||||||
|
end
|
||||||
|
|
||||||
def apply_operation(data, {:set_notebook_name, _client_pid, name}) do
|
def apply_operation(data, {:set_notebook_name, _client_pid, name}) do
|
||||||
data
|
data
|
||||||
|> with_actions()
|
|> with_actions()
|
||||||
|
@ -1053,6 +1061,18 @@ defmodule Livebook.Session.Data do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp erase_outputs({data, _} = data_actions) do
|
||||||
|
data_actions
|
||||||
|
|> clear_all_evaluation()
|
||||||
|
|> set!(
|
||||||
|
notebook:
|
||||||
|
Notebook.update_cells(data.notebook, fn
|
||||||
|
%Cell.Elixir{} = cell -> %{cell | outputs: []}
|
||||||
|
cell -> cell
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
defp set_notebook_name({data, _} = data_actions, name) do
|
defp set_notebook_name({data, _} = data_actions, name) do
|
||||||
data_actions
|
data_actions
|
||||||
|> set!(notebook: %{data.notebook | name: name})
|
|> set!(notebook: %{data.notebook | name: name})
|
||||||
|
|
|
@ -202,6 +202,16 @@ defmodule LivebookWeb.SessionLive do
|
||||||
<.remix_icon icon="more-2-fill" class="text-xl" />
|
<.remix_icon icon="more-2-fill" class="text-xl" />
|
||||||
</button>
|
</button>
|
||||||
<div class="menu" data-content>
|
<div class="menu" data-content>
|
||||||
|
<%= live_patch to: Routes.session_path(@socket, :export, @session.id, "livemd"),
|
||||||
|
class: "menu__item text-gray-500" do %>
|
||||||
|
<.remix_icon icon="download-2-line" />
|
||||||
|
<span class="font-medium">Export</span>
|
||||||
|
<% end %>
|
||||||
|
<button class="text-gray-500 menu__item"
|
||||||
|
phx-click="erase_outputs">
|
||||||
|
<.remix_icon icon="eraser-fill" />
|
||||||
|
<span class="font-medium">Erase outputs</span>
|
||||||
|
</button>
|
||||||
<button class="text-gray-500 menu__item"
|
<button class="text-gray-500 menu__item"
|
||||||
phx-click="fork_session">
|
phx-click="fork_session">
|
||||||
<.remix_icon icon="git-branch-line" />
|
<.remix_icon icon="git-branch-line" />
|
||||||
|
@ -213,11 +223,6 @@ defmodule LivebookWeb.SessionLive do
|
||||||
<.remix_icon icon="dashboard-2-line" />
|
<.remix_icon icon="dashboard-2-line" />
|
||||||
<span class="font-medium">See on Dashboard</span>
|
<span class="font-medium">See on Dashboard</span>
|
||||||
</a>
|
</a>
|
||||||
<%= live_patch to: Routes.session_path(@socket, :export, @session.id, "livemd"),
|
|
||||||
class: "menu__item text-gray-500" do %>
|
|
||||||
<.remix_icon icon="download-2-line" />
|
|
||||||
<span class="font-medium">Export</span>
|
|
||||||
<% end %>
|
|
||||||
<%= live_patch to: Routes.home_path(@socket, :close_session, @session.id),
|
<%= live_patch to: Routes.home_path(@socket, :close_session, @session.id),
|
||||||
class: "menu__item text-red-600" do %>
|
class: "menu__item text-red-600" do %>
|
||||||
<.remix_icon icon="close-circle-line" />
|
<.remix_icon icon="close-circle-line" />
|
||||||
|
@ -697,6 +702,11 @@ defmodule LivebookWeb.SessionLive do
|
||||||
{:noreply, create_session(socket, notebook: notebook, copy_images_from: images_dir)}
|
{:noreply, create_session(socket, notebook: notebook, copy_images_from: images_dir)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_event("erase_outputs", %{}, socket) do
|
||||||
|
Session.erase_outputs(socket.assigns.session.pid)
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
|
||||||
def handle_event("location_report", report, socket) do
|
def handle_event("location_report", report, socket) do
|
||||||
Phoenix.PubSub.broadcast_from(
|
Phoenix.PubSub.broadcast_from(
|
||||||
Livebook.PubSub,
|
Livebook.PubSub,
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<div class="shadow-custom-1 max-w-2xl flex items-center space-x-3 rounded-lg px-4 py-2 border-l-4 rounded-l-none border-yellow-300 bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-500 cursor-pointer" role="alert"
|
<div class="shadow-custom-1 max-w-2xl flex items-center space-x-3 rounded-lg px-4 py-2 border-l-4 rounded-l-none border-yellow-300 bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-500 cursor-pointer" role="alert"
|
||||||
phx-click="lv:clear-flash"
|
phx-click="lv:clear-flash"
|
||||||
phx-value-key="warning">
|
phx-value-key="warning">
|
||||||
<.remix_icon icon="error-warning-line" class="text-2xl text-yellow-400" />
|
<.remix_icon icon="alert-line" class="text-2xl text-yellow-400" />
|
||||||
<span class="whitespace-pre-wrap"><%= live_flash(@flash, :warning) %></span>
|
<span class="whitespace-pre-wrap"><%= live_flash(@flash, :warning) %></span>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -2515,6 +2515,73 @@ defmodule Livebook.Session.DataTest do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "apply_operation/2 given :erase_outputs" do
|
||||||
|
test "clears all sections evaluation and queues" do
|
||||||
|
data =
|
||||||
|
data_after_operations!([
|
||||||
|
{:insert_section, self(), 0, "s1"},
|
||||||
|
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||||
|
{:insert_cell, self(), "s1", 1, :elixir, "c2"},
|
||||||
|
{:insert_section, self(), 1, "s2"},
|
||||||
|
{:insert_cell, self(), "s2", 0, :elixir, "c3"},
|
||||||
|
{:set_section_parent, self(), "s2", "s1"},
|
||||||
|
{:set_runtime, self(), NoopRuntime.new()},
|
||||||
|
{:queue_cell_evaluation, self(), "c1"},
|
||||||
|
{:add_cell_evaluation_response, self(), "c1", @eval_resp, @eval_meta},
|
||||||
|
{:queue_cell_evaluation, self(), "c2"},
|
||||||
|
{:queue_cell_evaluation, self(), "c3"}
|
||||||
|
])
|
||||||
|
|
||||||
|
operation = {:erase_outputs, self()}
|
||||||
|
|
||||||
|
assert {:ok,
|
||||||
|
%{
|
||||||
|
cell_infos: %{
|
||||||
|
"c1" => %{validity_status: :aborted, evaluation_status: :ready},
|
||||||
|
"c2" => %{validity_status: :aborted, evaluation_status: :ready},
|
||||||
|
"c3" => %{validity_status: :fresh, evaluation_status: :ready}
|
||||||
|
},
|
||||||
|
section_infos: %{
|
||||||
|
"s1" => %{evaluating_cell_id: nil, evaluation_queue: []},
|
||||||
|
"s2" => %{evaluating_cell_id: nil, evaluation_queue: []}
|
||||||
|
}
|
||||||
|
}, _actions} = Data.apply_operation(data, operation)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "removes elixir cell outputs" do
|
||||||
|
data =
|
||||||
|
data_after_operations!([
|
||||||
|
{:insert_section, self(), 0, "s1"},
|
||||||
|
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||||
|
{:insert_cell, self(), "s1", 1, :markdown, "c2"},
|
||||||
|
{:insert_cell, self(), "s1", 2, :elixir, "c3"},
|
||||||
|
{:set_runtime, self(), NoopRuntime.new()},
|
||||||
|
{:queue_cell_evaluation, self(), "c1"},
|
||||||
|
{:add_cell_evaluation_response, self(), "c1", @eval_resp, @eval_meta},
|
||||||
|
{:queue_cell_evaluation, self(), "c3"},
|
||||||
|
{:add_cell_evaluation_response, self(), "c3", @eval_resp, @eval_meta}
|
||||||
|
])
|
||||||
|
|
||||||
|
operation = {:erase_outputs, self()}
|
||||||
|
|
||||||
|
assert {:ok,
|
||||||
|
%{
|
||||||
|
notebook: %{
|
||||||
|
sections: [
|
||||||
|
%{
|
||||||
|
id: "s1",
|
||||||
|
cells: [
|
||||||
|
%{id: "c1", outputs: []},
|
||||||
|
%{id: "c2"},
|
||||||
|
%{id: "c3", outputs: []}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}, _actions} = Data.apply_operation(data, operation)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "apply_operation/2 given :set_notebook_name" do
|
describe "apply_operation/2 given :set_notebook_name" do
|
||||||
test "updates notebook name with the given string" do
|
test "updates notebook name with the given string" do
|
||||||
data = Data.new()
|
data = Data.new()
|
||||||
|
|
Loading…
Add table
Reference in a new issue