refac
This commit is contained in:
@@ -45,7 +45,10 @@ class AutomationRun(Base):
|
|||||||
error = Column(Text, nullable=True)
|
error = Column(Text, nullable=True)
|
||||||
created_at = Column(BigInteger, nullable=False)
|
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
|
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(
|
def get_by_automation(
|
||||||
self,
|
self,
|
||||||
automation_id: str,
|
automation_id: str,
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ def check_automation_access(automation, user):
|
|||||||
|
|
||||||
|
|
||||||
def enrich_automation(automation: AutomationModel, db: Session, tz: str = None) -> AutomationResponse:
|
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)
|
last_run = AutomationRuns.get_latest(automation.id, db=db)
|
||||||
return AutomationResponse(
|
return AutomationResponse(
|
||||||
**automation.model_dump(),
|
**automation.model_dump(),
|
||||||
@@ -97,8 +98,18 @@ async def get_automation_items(
|
|||||||
db=db,
|
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 {
|
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,
|
'total': result.total,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,8 +47,8 @@
|
|||||||
|
|
||||||
let page = 1;
|
let page = 1;
|
||||||
|
|
||||||
// Debounce only query changes
|
// Debounce only query changes (gate behind loaded to prevent double-fetch on mount)
|
||||||
$: if (query !== undefined) {
|
$: if (loaded && query !== undefined) {
|
||||||
loading = true;
|
loading = true;
|
||||||
clearTimeout(searchDebounceTimer);
|
clearTimeout(searchDebounceTimer);
|
||||||
searchDebounceTimer = setTimeout(() => {
|
searchDebounceTimer = setTimeout(() => {
|
||||||
@@ -57,8 +57,8 @@
|
|||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Immediate response to page/filter changes
|
// Immediate response to page/filter changes (gate behind loaded)
|
||||||
$: if (page && statusFilter !== undefined) {
|
$: if (loaded && page && statusFilter !== undefined) {
|
||||||
getAutomationList();
|
getAutomationList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,6 +171,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
loaded = true;
|
loaded = true;
|
||||||
|
// Explicit initial fetch — reactive blocks will handle subsequent changes
|
||||||
|
await getAutomationList();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(searchDebounceTimer);
|
clearTimeout(searchDebounceTimer);
|
||||||
|
|||||||
Reference in New Issue
Block a user