Warranties: fix N+1 slowness, filter alignment, and Covers hover
- Perf: list_warranties did a db.session.get(Asset) per link per warranty (~1.8s for the full list). Eager-load the links and batch-fetch every linked asset in one query -> ~0.19s. - Filters: the "Status" label wrapped its select onto a second line, so the dropdown sat above the search box; keep the label inline so they align. - Covers: each asset chip now shows the asset name (often the hostname/alias) on hover, keeping the machine number as the label.
This commit is contained in:
@@ -51,7 +51,7 @@
|
||||
<td>{{ w.enddate ? formatDate(w.enddate) : '-' }}</td>
|
||||
<td>
|
||||
<span v-if="!w.assets.length" class="muted">-</span>
|
||||
<router-link v-for="a in w.assets" :key="a.assetid" :to="assetLink(a)" class="asset-chip">{{ a.assetnumber }}</router-link>
|
||||
<router-link v-for="a in w.assets" :key="a.assetid" :to="assetLink(a)" class="asset-chip" :title="a.name || a.assetnumber">{{ a.assetnumber }}</router-link>
|
||||
</td>
|
||||
<td class="actions">
|
||||
<button v-if="w.provider !== 'manual'" class="btn btn-secondary btn-sm" @click="refresh(w)">Refresh</button>
|
||||
@@ -350,6 +350,9 @@ async function refresh(w) {
|
||||
.muted { color: var(--text-light); }
|
||||
.status-badge { padding: 0.15rem 0.6rem; border-radius: 12px; font-size: 0.78rem; font-weight: 600; }
|
||||
.filters { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; }
|
||||
/* Keep the "Status" label + its select on one line so it aligns with the
|
||||
single-line search box next to it. */
|
||||
.filters label { display: inline-flex; align-items: center; gap: 0.4rem; }
|
||||
.filters .form-control { max-width: 320px; }
|
||||
.result-count { color: var(--text-light); font-size: 0.85rem; }
|
||||
.servicelevel-cell { max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
|
||||
@@ -9,6 +9,7 @@ from datetime import date, datetime, timezone
|
||||
|
||||
from flask import Blueprint, request
|
||||
from flask_jwt_extended import jwt_required
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from shopdb.api import (
|
||||
db, Asset,
|
||||
@@ -41,12 +42,15 @@ def _asset_summary(asset):
|
||||
}
|
||||
|
||||
|
||||
def _warranty_payload(warranty, today=None):
|
||||
"""to_dict plus the linked-asset summaries."""
|
||||
def _warranty_payload(warranty, today=None, assetmap=None):
|
||||
"""to_dict plus the linked-asset summaries. Pass assetmap (assetid -> Asset)
|
||||
to avoid a per-link query when serializing a list; without it, falls back to
|
||||
a per-link get (fine for a single warranty)."""
|
||||
data = warranty.to_dict(today)
|
||||
assets = []
|
||||
for link in warranty.links:
|
||||
asset = db.session.get(Asset, link.assetid)
|
||||
asset = assetmap.get(link.assetid) if assetmap is not None \
|
||||
else db.session.get(Asset, link.assetid)
|
||||
if asset:
|
||||
assets.append(_asset_summary(asset))
|
||||
data['assets'] = assets
|
||||
@@ -86,10 +90,17 @@ def list_warranties():
|
||||
if assetid:
|
||||
query = (query.join(WarrantyAsset, WarrantyAsset.warrantyid == Warranty.warrantyid)
|
||||
.filter(WarrantyAsset.assetid == assetid))
|
||||
warranties = query.order_by(Warranty.enddate.is_(None), Warranty.enddate).all()
|
||||
warranties = (query.options(joinedload(Warranty.links))
|
||||
.order_by(Warranty.enddate.is_(None), Warranty.enddate).all())
|
||||
|
||||
# Batch-fetch every linked asset in ONE query (was N+1: a db.session.get per
|
||||
# link per warranty, ~1.8s for the full list).
|
||||
assetids = {link.assetid for w in warranties for link in w.links}
|
||||
assetmap = ({a.assetid: a for a in Asset.query.filter(Asset.assetid.in_(assetids)).all()}
|
||||
if assetids else {})
|
||||
|
||||
today = date.today()
|
||||
items = [_warranty_payload(w, today) for w in warranties]
|
||||
items = [_warranty_payload(w, today, assetmap) for w in warranties]
|
||||
|
||||
status_filter = request.args.get('status')
|
||||
if status_filter:
|
||||
|
||||
Reference in New Issue
Block a user