Add changed cell indicator (#105)

This commit is contained in:
Jonatan Kłosko 2021-03-22 15:43:31 +01:00 committed by GitHub
parent 5e20541774
commit 9e69e8f096
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 111 additions and 51 deletions

View file

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

View file

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

View file

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