From 3254648849a0afb9d4b32c27970445f771433fe5 Mon Sep 17 00:00:00 2001 From: bot-ci Date: Mon, 2 Nov 2026 19:52:46 +0800 Subject: [PATCH] fix(system): sort recent tasks by started_at desc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 「最近任务」原先按 kv 键名排序。改为后端 `_task_records` 按 started_at 倒序写入 dict(缺开始时间排末尾,含进行中); 前端不再参与排序。配套 test_system_tasks_sorted_by_started_at_desc。 --- reaction_board/web/queries.py | 21 +++++++++++++---- tests/test_web_queries.py | 43 +++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/reaction_board/web/queries.py b/reaction_board/web/queries.py index 5db1331..ff0a59f 100644 --- a/reaction_board/web/queries.py +++ b/reaction_board/web/queries.py @@ -622,19 +622,32 @@ class DashboardQueries: continue return None + @staticmethod + def _task_started_sort_key(value: Any) -> tuple[int, int]: + """缺 started_at 的排末尾;有值的按开始时间倒序(含进行中)。""" + if isinstance(value, Mapping): + raw = value.get("started_at") + try: + return (0, -int(raw)) + except (TypeError, ValueError): + pass + return (1, 0) + def _task_records(self, conn: sqlite3.Connection) -> dict[str, Any]: rows = conn.execute( - "SELECT k, v FROM kv WHERE k LIKE 'task:last:%' ORDER BY k" + "SELECT k, v FROM kv WHERE k LIKE 'task:last:%'" ).fetchall() - result: dict[str, Any] = {} + items: list[tuple[str, Any]] = [] for row in rows: name = str(row["k"]).removeprefix("task:last:") try: value = json.loads(str(row["v"])) except (TypeError, json.JSONDecodeError): value = {"raw": row["v"]} - result[name] = value - return result + items.append((name, value)) + items.sort(key=lambda pair: self._task_started_sort_key(pair[1])) + # 插入序即开始时间倒序;前端 Object.entries 保留该序,勿再排序。 + return dict(items) def _task_running(self, conn: sqlite3.Connection) -> dict[str, Any]: rows = conn.execute( diff --git a/tests/test_web_queries.py b/tests/test_web_queries.py index 5ab401b..d4e2f2e 100644 --- a/tests/test_web_queries.py +++ b/tests/test_web_queries.py @@ -681,6 +681,48 @@ def test_overview_adjacent_previous_with_seed_and_dual_fields(): finally: db.close() + +def test_system_tasks_sorted_by_started_at_desc(): + """最近任务按 started_at 倒序;缺开始时间的排末尾(含进行中无 finished)。""" + import json + + with tempfile.TemporaryDirectory() as tmp: + db, q = setup(tmp) + try: + repo = Repository(db) + # 故意打乱写入/键名字母序;finished_at 与 started_at 错开,避免误用完成时间 + samples = ( + # name, started_at, finished_at + ("zzz_old", FIXED_NOW - 300, FIXED_NOW - 10), # 开始最早,完成却很晚 + ("aaa_mid", FIXED_NOW - 100, FIXED_NOW - 90), + ("mmm_new", FIXED_NOW - 10, None), # 进行中:只有 started + ("no_time", None, FIXED_NOW), # 缺 started,应末尾 + ) + for name, started, finished in samples: + payload = { + "name": name, + "ok": True if finished is not None else True, + "error": None, + "summary": {}, + } + if started is not None: + payload["started_at"] = started + if finished is not None: + payload["finished_at"] = finished + repo.kv_set(f"task:last:{name}", json.dumps(payload)) + for legacy in ("snapshot", "fans", "discovery"): + repo.kv_delete(f"task:last:{legacy}") + DASHBOARD_CACHE.clear() + system = q.system() + assert list(system["tasks"].keys()) == [ + "mmm_new", "aaa_mid", "zzz_old", "no_time", + ] + overview = q.overview() + assert list(overview["tasks"].keys()) == list(system["tasks"].keys()) + finally: + db.close() + + def main_test(): test_overview() test_video_listing_filters_and_sorting() @@ -696,6 +738,7 @@ def main_test(): test_fans_candidates_system_and_export() test_system_status_uses_completed_date_and_running_flags() test_system_next_runs_follow_last_completion_not_wall_clock() + test_system_tasks_sorted_by_started_at_desc() test_growth_auto_falls_back_to_one_day() test_snapshot_recent_next_run_follows_completion() test_overview_adjacent_previous_with_seed_and_dual_fields() -- 2.54.0