Improve daily jobs search metadata and modal link
This commit is contained in:
parent
dc3eb2f73c
commit
ded71cb50f
@ -1,5 +1,12 @@
|
||||
from .routes_shared import * # noqa: F401,F403
|
||||
from .routes_shared import _format_datetime
|
||||
from .routes_shared import (
|
||||
_apply_overrides_to_run,
|
||||
_format_datetime,
|
||||
_get_or_create_settings,
|
||||
_get_ui_timezone,
|
||||
_infer_monthly_schedule_from_runs,
|
||||
_infer_schedule_map_from_runs,
|
||||
)
|
||||
|
||||
from sqlalchemy import and_, cast, func, or_, String
|
||||
import math
|
||||
@ -276,6 +283,65 @@ def _build_daily_jobs_results(patterns: list[str], page: int) -> dict:
|
||||
if not _is_section_allowed("daily_jobs"):
|
||||
return section
|
||||
|
||||
try:
|
||||
tz = _get_ui_timezone()
|
||||
except Exception:
|
||||
tz = None
|
||||
|
||||
try:
|
||||
target_date = datetime.now(tz).date() if tz else datetime.utcnow().date()
|
||||
except Exception:
|
||||
target_date = datetime.utcnow().date()
|
||||
|
||||
settings = _get_or_create_settings()
|
||||
missed_start_date = getattr(settings, "daily_jobs_start_date", None)
|
||||
|
||||
if tz:
|
||||
local_midnight = datetime(
|
||||
year=target_date.year,
|
||||
month=target_date.month,
|
||||
day=target_date.day,
|
||||
hour=0,
|
||||
minute=0,
|
||||
second=0,
|
||||
tzinfo=tz,
|
||||
)
|
||||
start_of_day = local_midnight.astimezone(datetime_module.timezone.utc).replace(tzinfo=None)
|
||||
end_of_day = (local_midnight + timedelta(days=1)).astimezone(datetime_module.timezone.utc).replace(tzinfo=None)
|
||||
else:
|
||||
start_of_day = datetime(
|
||||
year=target_date.year,
|
||||
month=target_date.month,
|
||||
day=target_date.day,
|
||||
hour=0,
|
||||
minute=0,
|
||||
second=0,
|
||||
)
|
||||
end_of_day = start_of_day + timedelta(days=1)
|
||||
|
||||
def _to_local(dt_utc):
|
||||
if not dt_utc or not tz:
|
||||
return dt_utc
|
||||
try:
|
||||
if dt_utc.tzinfo is None:
|
||||
dt_utc = dt_utc.replace(tzinfo=datetime_module.timezone.utc)
|
||||
return dt_utc.astimezone(tz)
|
||||
except Exception:
|
||||
return dt_utc
|
||||
|
||||
def _bucket_15min(dt_utc):
|
||||
d = _to_local(dt_utc)
|
||||
if not d:
|
||||
return None
|
||||
minute_bucket = (d.minute // 15) * 15
|
||||
return f"{d.hour:02d}:{minute_bucket:02d}"
|
||||
|
||||
def _is_success_status(value: str) -> bool:
|
||||
s = (value or "").strip().lower()
|
||||
if not s:
|
||||
return False
|
||||
return ("success" in s) or ("override" in s)
|
||||
|
||||
query = (
|
||||
db.session.query(
|
||||
Job.id.label("job_id"),
|
||||
@ -314,12 +380,71 @@ def _build_daily_jobs_results(patterns: list[str], page: int) -> dict:
|
||||
)
|
||||
_enrich_paging(section, total, current_page, total_pages)
|
||||
for row in rows:
|
||||
expected_times = (_infer_schedule_map_from_runs(row.job_id).get(target_date.weekday()) or [])
|
||||
if not expected_times:
|
||||
monthly = _infer_monthly_schedule_from_runs(row.job_id)
|
||||
if monthly:
|
||||
try:
|
||||
dom = int(monthly.get("day_of_month") or 0)
|
||||
except Exception:
|
||||
dom = 0
|
||||
mtimes = monthly.get("times") or []
|
||||
try:
|
||||
import calendar as _calendar
|
||||
last_dom = _calendar.monthrange(target_date.year, target_date.month)[1]
|
||||
except Exception:
|
||||
last_dom = target_date.day
|
||||
scheduled_dom = dom if (dom and dom <= last_dom) else last_dom
|
||||
if target_date.day == scheduled_dom:
|
||||
expected_times = list(mtimes)
|
||||
|
||||
runs_for_day = (
|
||||
JobRun.query.filter(
|
||||
JobRun.job_id == row.job_id,
|
||||
JobRun.run_at >= start_of_day,
|
||||
JobRun.run_at < end_of_day,
|
||||
)
|
||||
.order_by(JobRun.run_at.asc())
|
||||
.all()
|
||||
)
|
||||
run_count = len(runs_for_day)
|
||||
|
||||
last_status = "-"
|
||||
expected_display = expected_times[-1] if expected_times else "-"
|
||||
if run_count > 0:
|
||||
last_run = runs_for_day[-1]
|
||||
try:
|
||||
job_obj = Job.query.get(int(row.job_id))
|
||||
status_display, _override_applied, _override_level, _ov_id, _ov_reason = _apply_overrides_to_run(job_obj, last_run)
|
||||
if getattr(last_run, "missed", False):
|
||||
last_status = status_display or "Missed"
|
||||
else:
|
||||
last_status = status_display or (last_run.status or "-")
|
||||
except Exception:
|
||||
last_status = last_run.status or "-"
|
||||
expected_display = _bucket_15min(last_run.run_at) or expected_display
|
||||
else:
|
||||
try:
|
||||
today_local = datetime.now(tz).date() if tz else datetime.utcnow().date()
|
||||
except Exception:
|
||||
today_local = datetime.utcnow().date()
|
||||
if target_date > today_local:
|
||||
last_status = "Expected"
|
||||
elif target_date == today_local:
|
||||
last_status = "Expected"
|
||||
else:
|
||||
if missed_start_date and target_date < missed_start_date:
|
||||
last_status = "-"
|
||||
else:
|
||||
last_status = "Missed"
|
||||
|
||||
success_text = "Yes" if _is_success_status(last_status) else "No"
|
||||
section["items"].append(
|
||||
{
|
||||
"title": row.job_name or f"Job #{row.job_id}",
|
||||
"subtitle": f"{row.customer_name or '-'} | {row.backup_software or '-'} / {row.backup_type or '-'}",
|
||||
"meta": "",
|
||||
"link": url_for("main.daily_jobs"),
|
||||
"meta": f"Expected: {expected_display} | Successful: {success_text} | Runs: {run_count}",
|
||||
"link": url_for("main.daily_jobs", date=target_date.strftime("%Y-%m-%d"), open_job_id=row.job_id),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ -665,7 +665,7 @@ if (tStatus) tStatus.textContent = '';
|
||||
});
|
||||
}
|
||||
|
||||
function attachDailyJobsHandlers() {
|
||||
function attachDailyJobsHandlers() {
|
||||
var rows = document.querySelectorAll(".daily-job-row");
|
||||
if (!rows.length) {
|
||||
return;
|
||||
@ -771,9 +771,43 @@ if (tStatus) tStatus.textContent = '';
|
||||
});
|
||||
}
|
||||
|
||||
function autoOpenJobFromQuery() {
|
||||
try {
|
||||
var params = new URLSearchParams(window.location.search || "");
|
||||
var openJobId = (params.get("open_job_id") || "").trim();
|
||||
if (!openJobId) {
|
||||
return;
|
||||
}
|
||||
|
||||
var rows = document.querySelectorAll(".daily-job-row");
|
||||
var targetRow = null;
|
||||
rows.forEach(function (row) {
|
||||
if ((row.getAttribute("data-job-id") || "") === openJobId) {
|
||||
targetRow = row;
|
||||
}
|
||||
});
|
||||
|
||||
if (!targetRow) {
|
||||
return;
|
||||
}
|
||||
|
||||
targetRow.click();
|
||||
|
||||
params.delete("open_job_id");
|
||||
var nextQuery = params.toString();
|
||||
var nextUrl = window.location.pathname + (nextQuery ? ("?" + nextQuery) : "");
|
||||
if (window.history && window.history.replaceState) {
|
||||
window.history.replaceState({}, document.title, nextUrl);
|
||||
}
|
||||
} catch (e) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
bindInlineCreateForms();
|
||||
attachDailyJobsHandlers();
|
||||
autoOpenJobFromQuery();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
@ -18,6 +18,8 @@ This file documents all changes made to this project via Claude Code.
|
||||
- Changed global search visibility to only include sections accessible to the currently active role
|
||||
- Changed `docs/technical-notes-codex.md` with a dedicated Global Grouped Search section (route/UI/behavior/access rules) and latest test build digest for `v20260216-02-global-search`
|
||||
- Changed global search to support per-section pagination (previous/next) so results beyond the first 10 can be browsed per section while preserving the current query/state
|
||||
- Changed Daily Jobs search result metadata to include expected run time, success indicator, and run count for the selected day
|
||||
- Changed Daily Jobs search result links to open the same Daily Jobs modal flow via `open_job_id` (instead of only navigating to the overview page)
|
||||
|
||||
### Fixed
|
||||
- Fixed `/search` page crash (`TypeError: 'builtin_function_or_method' object is not iterable`) by replacing Jinja dict access from `section.items` to `section['items']` in `templates/main/search.html`
|
||||
|
||||
Loading…
Reference in New Issue
Block a user