presentation/scheduling/
calendar_view_widget.py 231 lịch tháng (chuyển từ ui/calendar_view.py)
ai_task_creator_dialog.py 208 tạo task bằng AI
task_actions.py 189 thêm/sửa/chạy/xoá/xem log một task
kanban_board_widget.py 98 cột Kanban + vùng thả file
run_history_dialog.py 82 lịch sử các lượt chạy
ai_task_import_dialog.py 81 nhập task từ file
ui/schedule_task_tab.py 297 dựng bảng + đổi chế độ xem
ui/calendar_view.py 10 vỏ chuyển tiếp
Plan ghi 4 file; thực tế cần 6. Hai file thêm là run_history_dialog.py và
task_actions.py — không tách thì schedule_task_tab.py còn 517 dòng, vẫn vượt
ngưỡng 400.
ai_task_import_dialog.py làm mixin chứ không phải hộp thoại rời: plan gọi nó
là dialog, nhưng thực tế nó là TAB THỨ HAI của cùng hộp thoại tạo task, dùng
chung phần xem trước và nút Xác nhận. Tách hẳn thì phải nhân đôi cả hai.
LẠI LỖI DECORATOR: script này tôi quên dùng bản có tính dòng @, nên một
@staticmethod bị bỏ lại mồ côi -> IndentationError. Đây là lần thứ tư cùng
một lỗi. Đã thêm bước dọn decorator mồ côi vào script.
756 test xanh. 16 checker chạy đều qua.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
83 lines
3.3 KiB
Python
83 lines
3.3 KiB
Python
"""Lịch sử các lượt chạy của một task — R08-T11.
|
|
|
|
Mở từ menu chuột phải trên thẻ Kanban. Chỉ đọc: liệt kê từng lượt đã chạy,
|
|
kết quả và log.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional
|
|
from PySide6.QtCore import Qt, Signal
|
|
from PySide6.QtWidgets import (
|
|
QAbstractItemView, QComboBox, QDialog, QDialogButtonBox, QHBoxLayout,
|
|
QLabel, QLineEdit, QListWidget, QListWidgetItem, QMenu, QMessageBox,
|
|
QPlainTextEdit, QPushButton, QScrollArea, QSizePolicy, QStackedWidget,
|
|
QTabBar, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget,
|
|
)
|
|
from ...core import tasks as taskrepo
|
|
from ...core.projects import list_projects
|
|
from ...core.tasks import STATUSES, chain_error, duplicate_task, new_task
|
|
from ...core.worker import AgentWorker
|
|
from ...i18n import on_language_changed, tr
|
|
from ...state import AppContext
|
|
from ...theme import current_palette
|
|
from ...ui.calendar_view import CalendarView
|
|
from ...ui.icons import icon
|
|
from ...ui.osutil import open_path
|
|
|
|
|
|
class _RunHistoryDialog(QDialog):
|
|
"""Run history of one task as a table (newest first): time, status, error;
|
|
double-click a row to open that run's artifact folder."""
|
|
|
|
def __init__(self, task: dict, parent=None):
|
|
super().__init__(parent)
|
|
self._task = task
|
|
self.setWindowTitle(f"{tr('schedtask.menu_history')} — {task.get('title', '')}")
|
|
self.resize(620, 380)
|
|
root = QVBoxLayout(self)
|
|
hint = QLabel(tr("schedtask.hist_hint"))
|
|
hint.setObjectName("hint")
|
|
root.addWidget(hint)
|
|
|
|
runs = list(reversed(task.get("runs", []) or []))
|
|
self.table = QTableWidget(len(runs), 4)
|
|
self.table.setHorizontalHeaderLabels([
|
|
tr("schedtask.hist_col_time"), tr("schedtask.hist_col_status"),
|
|
tr("schedtask.hist_col_run"), tr("schedtask.hist_col_error"),
|
|
])
|
|
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
|
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
|
for row, run in enumerate(runs):
|
|
ok = run.get("status") == "success"
|
|
cells = (
|
|
run.get("finished_at", ""),
|
|
str(run.get("status", "")),
|
|
run.get("run_id", ""),
|
|
(run.get("error") or "")[:200],
|
|
)
|
|
for col, text in enumerate(cells):
|
|
item = QTableWidgetItem(str(text))
|
|
if col == 0:
|
|
item.setData(Qt.UserRole, run.get("run_id", ""))
|
|
self.table.setItem(row, col, item)
|
|
self.table.resizeColumnsToContents()
|
|
self.table.horizontalHeader().setStretchLastSection(True)
|
|
self.table.itemDoubleClicked.connect(self._open_artifact)
|
|
root.addWidget(self.table, 1)
|
|
|
|
buttons = QDialogButtonBox(QDialogButtonBox.Close)
|
|
buttons.rejected.connect(self.reject)
|
|
buttons.accepted.connect(self.accept)
|
|
root.addWidget(buttons)
|
|
|
|
def _open_artifact(self, item: QTableWidgetItem) -> None:
|
|
first = self.table.item(item.row(), 0)
|
|
run_id = first.data(Qt.UserRole) if first else ""
|
|
if not run_id:
|
|
return
|
|
folder = taskrepo.ARTIFACTS_DIR / self._task["task_id"] / run_id
|
|
if folder.exists():
|
|
open_path(str(folder))
|