Show all executing pipeline workers in the status chip
The :propagation Oban queue runs with concurrency 2, so the grid
worker chain step and the 10-minute ASOS nudge can (and do) execute
in parallel. PipelineStatus.current/0 used to return only the
most-recently-started worker, which made the chip label flip to
"ASOS nudge" at every :00 / :10 tick even though HRRR was still
chugging through forecast hours behind it.
Replace the single :detail field with a list of running_detail
maps, each tagged with :grid or :asos so the chip can:
- Render one sub-line per currently-executing worker
- Sort them deterministically (grid before asos) so the eye
doesn't see rows swap
- Still override the grid entry with the forecast-hour progress
label when a propagation_pipeline_progress broadcast arrives
This commit is contained in:
parent
456e38d750
commit
c7a1e352a7
3 changed files with 87 additions and 34 deletions
|
|
@ -30,10 +30,14 @@ defmodule Microwaveprop.Propagation.PipelineStatus do
|
|||
|
||||
@type state :: :running | :idle | :stale | :unknown
|
||||
|
||||
@type worker_tag :: :grid | :asos
|
||||
|
||||
@type running_detail :: %{worker: worker_tag(), label: String.t()}
|
||||
|
||||
@type t :: %{
|
||||
state: state(),
|
||||
label: String.t(),
|
||||
detail: String.t() | nil,
|
||||
details: [running_detail()],
|
||||
last_update_at: DateTime.t() | nil
|
||||
}
|
||||
|
||||
|
|
@ -52,30 +56,41 @@ defmodule Microwaveprop.Propagation.PipelineStatus do
|
|||
|
||||
@spec current() :: t()
|
||||
def current do
|
||||
case running_worker() do
|
||||
worker when is_binary(worker) ->
|
||||
case running_workers() do
|
||||
[] ->
|
||||
build_idle_or_stale(latest_completed_at())
|
||||
|
||||
details ->
|
||||
%{
|
||||
state: :running,
|
||||
label: "Updating propagation",
|
||||
detail: base_running_detail(worker),
|
||||
details: details,
|
||||
last_update_at: latest_completed_at()
|
||||
}
|
||||
|
||||
nil ->
|
||||
build_idle_or_stale(latest_completed_at())
|
||||
end
|
||||
end
|
||||
|
||||
defp running_worker do
|
||||
Repo.one(
|
||||
from j in "oban_jobs",
|
||||
where: j.state == "executing" and j.worker in ^@workers,
|
||||
order_by: [desc: j.attempted_at],
|
||||
limit: 1,
|
||||
select: j.worker
|
||||
# Returns every pipeline worker currently in executing state, in a
|
||||
# stable grid-before-asos order so the chip sub-lines don't flicker
|
||||
# as jobs come and go.
|
||||
defp running_workers do
|
||||
from(j in "oban_jobs",
|
||||
where: j.state == "executing" and j.worker in ^@workers,
|
||||
distinct: j.worker,
|
||||
select: j.worker
|
||||
)
|
||||
|> Repo.all()
|
||||
|> Enum.sort_by(&worker_sort_key/1)
|
||||
|> Enum.map(&to_running_detail/1)
|
||||
end
|
||||
|
||||
defp to_running_detail(@grid_worker), do: %{worker: :grid, label: "HRRR run"}
|
||||
defp to_running_detail(@asos_worker), do: %{worker: :asos, label: "ASOS nudge"}
|
||||
|
||||
defp worker_sort_key(@grid_worker), do: 0
|
||||
defp worker_sort_key(@asos_worker), do: 1
|
||||
defp worker_sort_key(_), do: 99
|
||||
|
||||
defp latest_completed_at do
|
||||
case Repo.one(
|
||||
from j in "oban_jobs",
|
||||
|
|
@ -91,7 +106,7 @@ defmodule Microwaveprop.Propagation.PipelineStatus do
|
|||
end
|
||||
|
||||
defp build_idle_or_stale(nil) do
|
||||
%{state: :unknown, label: "Propagation status unknown", detail: nil, last_update_at: nil}
|
||||
%{state: :unknown, label: "Propagation status unknown", details: [], last_update_at: nil}
|
||||
end
|
||||
|
||||
defp build_idle_or_stale(%DateTime{} = last) do
|
||||
|
|
@ -101,23 +116,19 @@ defmodule Microwaveprop.Propagation.PipelineStatus do
|
|||
%{
|
||||
state: :stale,
|
||||
label: "Propagation data stale · last update #{format_age(age_minutes)} ago",
|
||||
detail: nil,
|
||||
details: [],
|
||||
last_update_at: last
|
||||
}
|
||||
else
|
||||
%{
|
||||
state: :idle,
|
||||
label: "Up to date · #{format_age(age_minutes)} ago",
|
||||
detail: nil,
|
||||
details: [],
|
||||
last_update_at: last
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
defp base_running_detail(@grid_worker), do: "HRRR run"
|
||||
defp base_running_detail(@asos_worker), do: "ASOS nudge"
|
||||
defp base_running_detail(_), do: nil
|
||||
|
||||
defp format_age(0), do: "just now"
|
||||
defp format_age(1), do: "1m"
|
||||
defp format_age(n) when n < 60, do: "#{n}m"
|
||||
|
|
|
|||
|
|
@ -412,13 +412,13 @@ defmodule MicrowavepropWeb.MapLive do
|
|||
attr :progress, :any, default: nil
|
||||
|
||||
defp pipeline_status_chip(assigns) do
|
||||
{main, detail} = chip_display_parts(assigns.status, assigns.progress)
|
||||
details = chip_detail_labels(assigns.status, assigns.progress)
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
|> assign(:display_main, main)
|
||||
|> assign(:display_detail, detail)
|
||||
|> assign(:display_title, if(detail, do: "#{main} — #{detail}", else: main))
|
||||
|> assign(:display_main, assigns.status.label)
|
||||
|> assign(:display_details, details)
|
||||
|> assign(:display_title, chip_title(assigns.status.label, details))
|
||||
|
||||
~H"""
|
||||
<div
|
||||
|
|
@ -435,8 +435,8 @@ defmodule MicrowavepropWeb.MapLive do
|
|||
/>
|
||||
<span class="opacity-90 break-words min-w-0">
|
||||
{@display_main}
|
||||
<%= if @display_detail do %>
|
||||
<span class="block text-[11px] opacity-70">{@display_detail}</span>
|
||||
<%= for d <- @display_details do %>
|
||||
<span class="block text-[11px] opacity-70">{d}</span>
|
||||
<% end %>
|
||||
</span>
|
||||
<% :idle -> %>
|
||||
|
|
@ -453,11 +453,23 @@ defmodule MicrowavepropWeb.MapLive do
|
|||
"""
|
||||
end
|
||||
|
||||
defp chip_display_parts(%{state: :running} = status, progress) do
|
||||
{status.label, PipelineStatus.running_detail(progress) || status.detail}
|
||||
# Map the PipelineStatus details list to a plain list of sub-line
|
||||
# strings. For the grid worker specifically, the forecast-hour
|
||||
# progress payload overrides the base label so the chip can show
|
||||
# "+3h" / "now" instead of the generic "HRRR run".
|
||||
defp chip_detail_labels(%{state: :running, details: details}, progress) do
|
||||
override = PipelineStatus.running_detail(progress)
|
||||
|
||||
Enum.map(details, fn
|
||||
%{worker: :grid} -> override || "HRRR run"
|
||||
%{label: label} -> label
|
||||
end)
|
||||
end
|
||||
|
||||
defp chip_display_parts(status, _progress), do: {status.label, nil}
|
||||
defp chip_detail_labels(_status, _progress), do: []
|
||||
|
||||
defp chip_title(main, []), do: main
|
||||
defp chip_title(main, details), do: "#{main} — #{Enum.join(details, " · ")}"
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ defmodule Microwaveprop.Propagation.PipelineStatusTest do
|
|||
assert is_binary(status.label)
|
||||
end
|
||||
|
||||
test "returns :running with grid worker detail when PropagationGridWorker is executing" do
|
||||
test "returns :running with a single grid-worker detail when only PropagationGridWorker is executing" do
|
||||
insert_oban_job(%{
|
||||
state: "executing",
|
||||
worker: @grid_worker,
|
||||
|
|
@ -52,10 +52,11 @@ defmodule Microwaveprop.Propagation.PipelineStatusTest do
|
|||
|
||||
assert status.state == :running
|
||||
assert status.label == "Updating propagation"
|
||||
assert status.detail =~ "HRRR"
|
||||
assert [%{worker: :grid, label: grid_label}] = status.details
|
||||
assert grid_label =~ "HRRR"
|
||||
end
|
||||
|
||||
test "returns :running with ASOS detail when AsosAdjustmentWorker is executing" do
|
||||
test "returns :running with a single ASOS detail when only AsosAdjustmentWorker is executing" do
|
||||
insert_oban_job(%{
|
||||
state: "executing",
|
||||
worker: @asos_worker,
|
||||
|
|
@ -66,7 +67,36 @@ defmodule Microwaveprop.Propagation.PipelineStatusTest do
|
|||
|
||||
assert status.state == :running
|
||||
assert status.label == "Updating propagation"
|
||||
assert status.detail =~ "ASOS"
|
||||
assert [%{worker: :asos, label: asos_label}] = status.details
|
||||
assert asos_label =~ "ASOS"
|
||||
end
|
||||
|
||||
test "returns :running with BOTH workers when grid + ASOS are executing at the same time" do
|
||||
# Queue concurrency was bumped to 2 so the chain-step grid worker
|
||||
# and the 10-minute ASOS nudge can run in parallel. The chip
|
||||
# should show one sub-line per running worker, in a deterministic
|
||||
# grid-before-asos order so the eye doesn't see them flicker.
|
||||
insert_oban_job(%{
|
||||
state: "executing",
|
||||
worker: @grid_worker,
|
||||
attempted_at: minutes_ago(5)
|
||||
})
|
||||
|
||||
insert_oban_job(%{
|
||||
state: "executing",
|
||||
worker: @asos_worker,
|
||||
attempted_at: minutes_ago(1)
|
||||
})
|
||||
|
||||
status = PipelineStatus.current()
|
||||
|
||||
assert status.state == :running
|
||||
assert status.label == "Updating propagation"
|
||||
assert [grid, asos] = status.details
|
||||
assert grid.worker == :grid
|
||||
assert grid.label =~ "HRRR"
|
||||
assert asos.worker == :asos
|
||||
assert asos.label =~ "ASOS"
|
||||
end
|
||||
|
||||
test "returns :idle with Up to date label when last grid run completed recently" do
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue