This commit is contained in:
Timothy Jaeryang Baek
2026-04-11 16:55:20 -06:00
parent 674695918e
commit 09f6d7ba57
3 changed files with 53 additions and 6 deletions

View File

@@ -45,7 +45,10 @@ class AutomationRun(Base):
error = Column(Text, nullable=True)
created_at = Column(BigInteger, nullable=False)
__table_args__ = (Index('ix_automation_run_automation_id', 'automation_id'),)
__table_args__ = (
Index('ix_automation_run_automation_id', 'automation_id'),
Index('ix_automation_run_aid_created', 'automation_id', 'created_at'),
)
####################
@@ -308,6 +311,37 @@ class AutomationRunTable:
)
return AutomationRunModel.model_validate(row) if row else None
def get_latest_batch(
self, automation_ids: list[str], db: Optional[Session] = None
) -> dict[str, AutomationRunModel]:
"""Fetch the latest run for each automation in a single query."""
if not automation_ids:
return {}
with get_db_context(db) as db:
# Subquery: max created_at per automation_id
subq = (
db.query(
AutomationRun.automation_id,
func.max(AutomationRun.created_at).label('max_created'),
)
.filter(AutomationRun.automation_id.in_(automation_ids))
.group_by(AutomationRun.automation_id)
.subquery()
)
rows = (
db.query(AutomationRun)
.join(
subq,
(AutomationRun.automation_id == subq.c.automation_id)
& (AutomationRun.created_at == subq.c.max_created),
)
.all()
)
return {
row.automation_id: AutomationRunModel.model_validate(row)
for row in rows
}
def get_by_automation(
self,
automation_id: str,

View File

@@ -61,6 +61,7 @@ def check_automation_access(automation, user):
def enrich_automation(automation: AutomationModel, db: Session, tz: str = None) -> AutomationResponse:
"""Full enrichment for single-item views (includes next_runs computation)."""
last_run = AutomationRuns.get_latest(automation.id, db=db)
return AutomationResponse(
**automation.model_dump(),
@@ -97,8 +98,18 @@ async def get_automation_items(
db=db,
)
# Batch-fetch latest runs in a single query instead of N+1
ids = [item.id for item in result.items]
latest_runs = AutomationRuns.get_latest_batch(ids, db=db) if ids else {}
return {
'items': [enrich_automation(item, db, tz=user.timezone) for item in result.items],
'items': [
AutomationResponse(
**item.model_dump(),
last_run=latest_runs.get(item.id),
)
for item in result.items
],
'total': result.total,
}

View File

@@ -47,8 +47,8 @@
let page = 1;
// Debounce only query changes
$: if (query !== undefined) {
// Debounce only query changes (gate behind loaded to prevent double-fetch on mount)
$: if (loaded && query !== undefined) {
loading = true;
clearTimeout(searchDebounceTimer);
searchDebounceTimer = setTimeout(() => {
@@ -57,8 +57,8 @@
}, 300);
}
// Immediate response to page/filter changes
$: if (page && statusFilter !== undefined) {
// Immediate response to page/filter changes (gate behind loaded)
$: if (loaded && page && statusFilter !== undefined) {
getAutomationList();
}
@@ -171,6 +171,8 @@
}
loaded = true;
// Explicit initial fetch — reactive blocks will handle subsequent changes
await getAutomationList();
return () => {
clearTimeout(searchDebounceTimer);