mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-11-10 14:11:29 +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(),
|
revision: cell_revision(),
|
||||||
deltas: list(Delta.t()),
|
deltas: list(Delta.t()),
|
||||||
revision_by_client_pid: %{pid() => cell_revision()},
|
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()
|
@type cell_revision :: non_neg_integer()
|
||||||
|
|
@ -125,7 +126,7 @@ defmodule Livebook.Session.Data do
|
||||||
for section <- notebook.sections,
|
for section <- notebook.sections,
|
||||||
cell <- section.cells,
|
cell <- section.cells,
|
||||||
into: %{},
|
into: %{},
|
||||||
do: {cell.id, new_cell_info([])}
|
do: {cell.id, new_cell_info(cell, [])}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
@ -415,7 +416,7 @@ defmodule Livebook.Session.Data do
|
||||||
data_actions
|
data_actions
|
||||||
|> set!(
|
|> set!(
|
||||||
notebook: Notebook.insert_cell(data.notebook, section_id, index, cell),
|
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
|
end
|
||||||
|
|
||||||
|
|
@ -512,8 +513,7 @@ defmodule Livebook.Session.Data do
|
||||||
data_actions
|
data_actions
|
||||||
|> set_cell_info!(cell.id,
|
|> set_cell_info!(cell.id,
|
||||||
validity_status: :evaluated,
|
validity_status: :evaluated,
|
||||||
evaluation_status: :ready,
|
evaluation_status: :ready
|
||||||
evaluated_at: DateTime.utc_now()
|
|
||||||
)
|
)
|
||||||
|> set_section_info!(section.id, evaluating_cell_id: nil)
|
|> set_section_info!(section.id, evaluating_cell_id: nil)
|
||||||
end
|
end
|
||||||
|
|
@ -553,7 +553,9 @@ defmodule Livebook.Session.Data do
|
||||||
|
|
||||||
data_actions
|
data_actions
|
||||||
|> set!(notebook: Notebook.update_cell(data.notebook, id, &%{&1 | outputs: []}))
|
|> 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)
|
|> set_section_info!(section.id, evaluating_cell_id: id, evaluation_queue: ids)
|
||||||
|> add_action({:start_evaluation, cell, section})
|
|> 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: [])
|
|> set_section_info!(section.id, evaluating_cell_id: nil, evaluation_queue: [])
|
||||||
|> reduce(
|
|> reduce(
|
||||||
section.cells,
|
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
|
end
|
||||||
|
|
||||||
|
|
@ -651,10 +657,18 @@ defmodule Livebook.Session.Data do
|
||||||
|
|
||||||
new_source = JSInterop.apply_delta_to_string(transformed_new_delta, cell.source)
|
new_source = JSInterop.apply_delta_to_string(transformed_new_delta, cell.source)
|
||||||
|
|
||||||
|
new_digest = compute_digest(new_source)
|
||||||
|
|
||||||
data_actions
|
data_actions
|
||||||
|> set!(notebook: Notebook.update_cell(data.notebook, cell.id, &%{&1 | source: new_source}))
|
|> set!(notebook: Notebook.update_cell(data.notebook, cell.id, &%{&1 | source: new_source}))
|
||||||
|> update_cell_info!(cell.id, fn info ->
|
|> 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,
|
# 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.
|
# 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)
|
info = put_in(info.revision_by_client_pid[client_pid], info.revision)
|
||||||
|
|
@ -709,14 +723,15 @@ defmodule Livebook.Session.Data do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp new_cell_info(client_pids) do
|
defp new_cell_info(cell, client_pids) do
|
||||||
%{
|
%{
|
||||||
revision: 0,
|
revision: 0,
|
||||||
deltas: [],
|
deltas: [],
|
||||||
revision_by_client_pid: Map.new(client_pids, &{&1, 0}),
|
revision_by_client_pid: Map.new(client_pids, &{&1, 0}),
|
||||||
validity_status: :fresh,
|
validity_status: :fresh,
|
||||||
evaluation_status: :ready,
|
evaluation_status: :ready,
|
||||||
evaluated_at: nil
|
digest: compute_digest(cell.source),
|
||||||
|
evaluation_digest: nil
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -770,6 +785,8 @@ defmodule Livebook.Session.Data do
|
||||||
set!(data_actions, dirty: dirty)
|
set!(data_actions, dirty: dirty)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp compute_digest(source), do: :erlang.md5(source)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Finds the cell that's currently being evaluated in the given section.
|
Finds the cell that's currently being evaluated in the given section.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -249,55 +249,53 @@ defmodule LivebookWeb.CellComponent do
|
||||||
|> String.split("\n")
|
|> String.split("\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp render_cell_status(%{evaluation_status: :evaluating}) do
|
defp render_cell_status(%{evaluation_status: :evaluating} = info) do
|
||||||
assigns = %{}
|
render_status_indicator(
|
||||||
|
"Evaluating",
|
||||||
~L"""
|
"bg-blue-500",
|
||||||
<div class="flex items-center space-x-2">
|
"bg-blue-400",
|
||||||
<div class="text-xs text-gray-400">Evaluating</div>
|
info.digest != info.evaluation_digest
|
||||||
<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>
|
|
||||||
"""
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp render_cell_status(%{evaluation_status: :queued}) do
|
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"""
|
~L"""
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-1">
|
||||||
<div class="text-xs text-gray-400">Queued</div>
|
<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="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="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 bg-gray-500"></span>
|
<span class="relative inline-flex rounded-full h-3 w-3 <%= @circle_class %>"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
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
|
end
|
||||||
|
|
|
||||||
|
|
@ -665,6 +665,32 @@ defmodule Livebook.Session.DataTest do
|
||||||
}, []} = Data.apply_operation(data, operation)
|
}, []} = Data.apply_operation(data, operation)
|
||||||
end
|
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
|
test "marks next queued cell in this section as evaluating if there is one" do
|
||||||
data =
|
data =
|
||||||
data_after_operations!([
|
data_after_operations!([
|
||||||
|
|
@ -1048,6 +1074,25 @@ defmodule Livebook.Session.DataTest do
|
||||||
}, _actions} = Data.apply_operation(data, operation)
|
}, _actions} = Data.apply_operation(data, operation)
|
||||||
end
|
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
|
test "transforms the delta if the revision is not the most recent" do
|
||||||
client1_pid = IEx.Helpers.pid(0, 0, 0)
|
client1_pid = IEx.Helpers.pid(0, 0, 0)
|
||||||
client2_pid = IEx.Helpers.pid(0, 0, 1)
|
client2_pid = IEx.Helpers.pid(0, 0, 1)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue