feat: panel Timeline scatter (X=tiempo, Y=DAG, color=status) via ImPlot
- tabs.cpp draw_timeline: scatter ImPlot con eje X tiempo (UseLocalTime), eje Y categorico DAG (SetupAxisTicks), 1 serie por status con color consistente (verde/rojo/amarillo/gris). - Combo ventana: 15m/1h/6h/24h/7d. Default 24h. - Hover tooltip: punto mas cercano en pixel-space -> muestra status, dag, run id, started/finished, trigger, error. - main.cpp: g_runs_all cache. Snapshot inicial via list_runs_http(limit=200) + upserts desde WS deltas. Auto-refresh por g_refresh_pending. - Panel toggle "Timeline" en el menu View. - Helper parse_rfc3339 inline (ignora offset, asume hora local — coherente con ImPlot::UseLocalTime). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,11 +22,23 @@ static std::string g_ws_path = "/api/ws/dagruns";
|
||||
// Cache en memoria del primer fetch + ultimos eventos WS.
|
||||
static std::vector<dag_ui::DagInfo> g_dags;
|
||||
static std::vector<dag_ui::DagRunRow> g_live_runs; // upsert por id desde WS
|
||||
static std::vector<dag_ui::DagRunRow> g_runs_all; // cache para Timeline (snapshot REST + WS upsert)
|
||||
static long long g_ws_runs_wm = 0;
|
||||
static long long g_ws_steps_wm = 0;
|
||||
static int g_ws_msg_count = 0;
|
||||
static std::string g_last_error;
|
||||
|
||||
// Upsert por id en g_runs_all. Mantiene mas reciente al frente.
|
||||
static void upsert_run_in_all(const dag_ui::DagRunRow& r) {
|
||||
for (auto& existing : g_runs_all) {
|
||||
if (existing.id == r.id) {
|
||||
existing = r;
|
||||
return;
|
||||
}
|
||||
}
|
||||
g_runs_all.push_back(r);
|
||||
}
|
||||
|
||||
static WsClient g_ws;
|
||||
|
||||
// Toggles de paneles (visibles desde el menu View del menubar canonico)
|
||||
@@ -35,6 +47,7 @@ static bool g_show_live = true;
|
||||
static bool g_show_dag_list = true;
|
||||
static bool g_show_dag_detail = true;
|
||||
static bool g_show_run_detail = true;
|
||||
static bool g_show_timeline = true;
|
||||
|
||||
// Auto-fetch DAG list una vez al arrancar.
|
||||
static bool g_initial_fetched = false;
|
||||
@@ -75,6 +88,7 @@ static void parse_ws_payload(const std::string& payload) {
|
||||
r.finished_at = rj.value("finished_at", "");
|
||||
r.error = rj.value("error", "");
|
||||
upsert_live_run(r);
|
||||
upsert_run_in_all(r);
|
||||
// Cuando un run termina, refresca DAG List para que last_runs
|
||||
// refleje la nueva ejecucion en R1..R5.
|
||||
if (r.status == "success" || r.status == "failed" ||
|
||||
@@ -139,6 +153,11 @@ static void render() {
|
||||
g_initial_fetched = true;
|
||||
g_refresh_pending = false;
|
||||
dag_ui::list_dags_http(g_api_url, g_dags);
|
||||
// Tambien snapshot inicial / refresh de runs para Timeline.
|
||||
std::vector<dag_ui::DagRunRow> tmp;
|
||||
if (dag_ui::list_runs_http(g_api_url, "", 200, tmp)) {
|
||||
for (auto& r : tmp) upsert_run_in_all(r);
|
||||
}
|
||||
}
|
||||
|
||||
// Drain WS messages this frame (cheap, max 64).
|
||||
@@ -151,6 +170,7 @@ static void render() {
|
||||
if (g_show_dag_list) dag_ui_tabs::draw_dag_list(g_api_url, g_dags, g_live_runs);
|
||||
if (g_show_dag_detail) dag_ui_tabs::draw_dag_detail(g_api_url);
|
||||
if (g_show_run_detail) dag_ui_tabs::draw_run_detail(g_api_url);
|
||||
if (g_show_timeline) dag_ui_tabs::draw_timeline(g_api_url, g_runs_all);
|
||||
if (g_show_main) draw_main();
|
||||
if (g_show_live) draw_live();
|
||||
}
|
||||
@@ -163,6 +183,7 @@ int main(int /*argc*/, char** /*argv*/) {
|
||||
{ "DAGs", nullptr, &g_show_dag_list },
|
||||
{ "DAG Detail", nullptr, &g_show_dag_detail },
|
||||
{ "Run Detail", nullptr, &g_show_run_detail },
|
||||
{ "Timeline", nullptr, &g_show_timeline },
|
||||
{ "Live (WS)", nullptr, &g_show_live },
|
||||
{ "Main (diag)", nullptr, &g_show_main },
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user