mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-03-04 02:43:09 +08:00
Add changed cell indicator (#105)
This commit is contained in:
parent
5e20541774
commit
9e69e8f096
3 changed files with 111 additions and 51 deletions
|
@ -52,7 +52,8 @@ defmodule Livebook.Session.Data do
|
|||
revision: cell_revision(),
|
||||
deltas: list(Delta.t()),
|
||||
revision_by_client_pid: %{pid() => cell_revision()},
|
||||
evaluated_at: DateTime.t()
|
||||
digest: String.t(),
|
||||
evaluation_digest: String.t() | nil
|
||||
}
|
||||
|
||||
@type cell_revision :: non_neg_integer()
|
||||
|
@ -125,7 +126,7 @@ defmodule Livebook.Session.Data do
|
|||
for section <- notebook.sections,
|
||||
cell <- section.cells,
|
||||
into: %{},
|
||||
do: {cell.id, new_cell_info([])}
|
||||
do: {cell.id, new_cell_info(cell, [])}
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -415,7 +416,7 @@ defmodule Livebook.Session.Data do
|
|||
data_actions
|
||||
|> set!(
|
||||
notebook: Notebook.insert_cell(data.notebook, section_id, index, cell),
|
||||
cell_infos: Map.put(data.cell_infos, cell.id, new_cell_info(data.client_pids))
|
||||
cell_infos: Map.put(data.cell_infos, cell.id, new_cell_info(cell, data.client_pids))
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -512,8 +513,7 @@ defmodule Livebook.Session.Data do
|
|||
data_actions
|
||||
|> set_cell_info!(cell.id,
|
||||
validity_status: :evaluated,
|
||||
evaluation_status: :ready,
|
||||
evaluated_at: DateTime.utc_now()
|
||||
evaluation_status: :ready
|
||||
)
|
||||
|> set_section_info!(section.id, evaluating_cell_id: nil)
|
||||
end
|
||||
|
@ -553,7 +553,9 @@ defmodule Livebook.Session.Data do
|
|||
|
||||
data_actions
|
||||
|> set!(notebook: Notebook.update_cell(data.notebook, id, &%{&1 | outputs: []}))
|
||||
|> set_cell_info!(id, evaluation_status: :evaluating)
|
||||
|> update_cell_info!(id, fn info ->
|
||||
%{info | evaluation_status: :evaluating, evaluation_digest: info.digest}
|
||||
end)
|
||||
|> set_section_info!(section.id, evaluating_cell_id: id, evaluation_queue: ids)
|
||||
|> add_action({:start_evaluation, cell, section})
|
||||
|
||||
|
@ -575,7 +577,11 @@ defmodule Livebook.Session.Data do
|
|||
|> set_section_info!(section.id, evaluating_cell_id: nil, evaluation_queue: [])
|
||||
|> reduce(
|
||||
section.cells,
|
||||
&set_cell_info!(&1, &2.id, validity_status: :fresh, evaluation_status: :ready)
|
||||
&set_cell_info!(&1, &2.id,
|
||||
validity_status: :fresh,
|
||||
evaluation_status: :ready,
|
||||
evaluation_digest: nil
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -651,10 +657,18 @@ defmodule Livebook.Session.Data do
|
|||
|
||||
new_source = JSInterop.apply_delta_to_string(transformed_new_delta, cell.source)
|
||||
|
||||
new_digest = compute_digest(new_source)
|
||||
|
||||
data_actions
|
||||
|> set!(notebook: Notebook.update_cell(data.notebook, cell.id, &%{&1 | source: new_source}))
|
||||
|> update_cell_info!(cell.id, fn info ->
|
||||
info = %{info | deltas: info.deltas ++ [transformed_new_delta], revision: info.revision + 1}
|
||||
info = %{
|
||||
info
|
||||
| deltas: info.deltas ++ [transformed_new_delta],
|
||||
revision: info.revision + 1,
|
||||
digest: new_digest
|
||||
}
|
||||
|
||||
# Before receiving acknowledgement, the client receives all the other deltas,
|
||||
# so we can assume they are in sync with the server and have the same revision.
|
||||
info = put_in(info.revision_by_client_pid[client_pid], info.revision)
|
||||
|
@ -709,14 +723,15 @@ defmodule Livebook.Session.Data do
|
|||
}
|
||||
end
|
||||
|
||||
defp new_cell_info(client_pids) do
|
||||
defp new_cell_info(cell, client_pids) do
|
||||
%{
|
||||
revision: 0,
|
||||
deltas: [],
|
||||
revision_by_client_pid: Map.new(client_pids, &{&1, 0}),
|
||||
validity_status: :fresh,
|
||||
evaluation_status: :ready,
|
||||
evaluated_at: nil
|
||||
digest: compute_digest(cell.source),
|
||||
evaluation_digest: nil
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -770,6 +785,8 @@ defmodule Livebook.Session.Data do
|
|||
set!(data_actions, dirty: dirty)
|
||||
end
|
||||
|
||||
defp compute_digest(source), do: :erlang.md5(source)
|
||||
|
||||
@doc """
|
||||
Finds the cell that's currently being evaluated in the given section.
|
||||
"""
|
||||
|
|
|
@ -249,55 +249,53 @@ defmodule LivebookWeb.CellComponent do
|
|||
|> String.split("\n")
|
||||
end
|
||||
|
||||
defp render_cell_status(%{evaluation_status: :evaluating}) do
|
||||
assigns = %{}
|
||||
|
||||
~L"""
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="text-xs text-gray-400">Evaluating</div>
|
||||
<span class="flex relative h-3 w-3">
|
||||
<span class="animate-ping absolute inline-flex h-3 w-3 rounded-full bg-blue-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-3 w-3 bg-blue-500"></span>
|
||||
</span>
|
||||
</div>
|
||||
"""
|
||||
defp render_cell_status(%{evaluation_status: :evaluating} = info) do
|
||||
render_status_indicator(
|
||||
"Evaluating",
|
||||
"bg-blue-500",
|
||||
"bg-blue-400",
|
||||
info.digest != info.evaluation_digest
|
||||
)
|
||||
end
|
||||
|
||||
defp render_cell_status(%{evaluation_status: :queued}) do
|
||||
assigns = %{}
|
||||
render_status_indicator("Queued", "bg-gray-500", "bg-gray-400", false)
|
||||
end
|
||||
|
||||
defp render_cell_status(%{validity_status: :evaluated} = info) do
|
||||
render_status_indicator(
|
||||
"Evaluated",
|
||||
"bg-green-400",
|
||||
nil,
|
||||
info.digest != info.evaluation_digest
|
||||
)
|
||||
end
|
||||
|
||||
defp render_cell_status(%{validity_status: :stale} = info) do
|
||||
render_status_indicator("Stale", "bg-yellow-200", nil, info.digest != info.evaluation_digest)
|
||||
end
|
||||
|
||||
defp render_cell_status(_), do: nil
|
||||
|
||||
defp render_status_indicator(text, circle_class, animated_circle_class, show_changed) do
|
||||
assigns = %{
|
||||
text: text,
|
||||
circle_class: circle_class,
|
||||
animated_circle_class: animated_circle_class,
|
||||
show_changed: show_changed
|
||||
}
|
||||
|
||||
~L"""
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="text-xs text-gray-400">Queued</div>
|
||||
<div class="flex items-center space-x-1">
|
||||
<div class="flex text-xs text-gray-400">
|
||||
<%= @text %>
|
||||
<span class="<%= unless(@show_changed, do: "invisible") %>">*</span>
|
||||
</div>
|
||||
<span class="flex relative h-3 w-3">
|
||||
<span class="animate-ping absolute inline-flex h-3 w-3 rounded-full bg-gray-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-3 w-3 bg-gray-500"></span>
|
||||
<span class="animate-ping absolute inline-flex h-3 w-3 rounded-full <%= @animated_circle_class %> opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-3 w-3 <%= @circle_class %>"></span>
|
||||
</span>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_cell_status(%{validity_status: :evaluated}) do
|
||||
assigns = %{}
|
||||
|
||||
~L"""
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="text-xs text-gray-400">Evaluated</div>
|
||||
<div class="h-3 w-3 rounded-full bg-green-400"></div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_cell_status(%{validity_status: :stale}) do
|
||||
assigns = %{}
|
||||
|
||||
~L"""
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="text-xs text-gray-400">Stale</div>
|
||||
<div class="h-3 w-3 rounded-full bg-yellow-200"></div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_cell_status(_), do: nil
|
||||
end
|
||||
|
|
|
@ -665,6 +665,32 @@ defmodule Livebook.Session.DataTest do
|
|||
}, []} = Data.apply_operation(data, operation)
|
||||
end
|
||||
|
||||
test "updates cell evaluation digest" do
|
||||
data =
|
||||
data_after_operations!([
|
||||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :elixir, "c1"},
|
||||
{:insert_cell, self(), "s1", 1, :elixir, "c2"},
|
||||
{:queue_cell_evaluation, self(), "c1"}
|
||||
])
|
||||
|
||||
%{cell_infos: %{"c1" => %{digest: digest}}} = data
|
||||
|
||||
operation = {:add_cell_evaluation_response, self(), "c1", {:ok, [1, 2, 3]}}
|
||||
|
||||
assert {:ok,
|
||||
%{
|
||||
cell_infos: %{
|
||||
"c1" => %{
|
||||
validity_status: :evaluated,
|
||||
evaluation_status: :ready,
|
||||
evaluation_digest: ^digest
|
||||
}
|
||||
},
|
||||
section_infos: %{"s1" => %{evaluating_cell_id: nil, evaluation_queue: []}}
|
||||
}, []} = Data.apply_operation(data, operation)
|
||||
end
|
||||
|
||||
test "marks next queued cell in this section as evaluating if there is one" do
|
||||
data =
|
||||
data_after_operations!([
|
||||
|
@ -1048,6 +1074,25 @@ defmodule Livebook.Session.DataTest do
|
|||
}, _actions} = Data.apply_operation(data, operation)
|
||||
end
|
||||
|
||||
test "updates cell digest based on the new content" do
|
||||
data =
|
||||
data_after_operations!([
|
||||
{:client_join, self()},
|
||||
{:insert_section, self(), 0, "s1"},
|
||||
{:insert_cell, self(), "s1", 0, :elixir, "c1"}
|
||||
])
|
||||
|
||||
%{cell_infos: %{"c1" => %{digest: digest}}} = data
|
||||
|
||||
delta = Delta.new() |> Delta.insert("cats")
|
||||
operation = {:apply_cell_delta, self(), "c1", delta, 1}
|
||||
|
||||
assert {:ok, %{cell_infos: %{"c1" => %{digest: new_digest}}}, _actions} =
|
||||
Data.apply_operation(data, operation)
|
||||
|
||||
assert digest != new_digest
|
||||
end
|
||||
|
||||
test "transforms the delta if the revision is not the most recent" do
|
||||
client1_pid = IEx.Helpers.pid(0, 0, 0)
|
||||
client2_pid = IEx.Helpers.pid(0, 0, 1)
|
||||
|
|
Loading…
Reference in a new issue