diff --git a/shopdb/core/api/reports.py b/shopdb/core/api/reports.py index c31d93f..238878e 100644 --- a/shopdb/core/api/reports.py +++ b/shopdb/core/api/reports.py @@ -467,9 +467,14 @@ def pc_relationships(): Query parameters: - format: 'json' (default) or 'csv' """ - # Asset relationships where a computer (source) relates to a machine - # (target) - the asset-model equivalent of the legacy PC->machine links. - sql = db.text(""" + # PC <-> machine relationships in EITHER direction. The import orients edges + # both ways: the general machinerelationships migration stores machine(source) + # -> PC(target), while the synthetic measuring-tool links store PC(source) -> + # tool(target). Match both so the report is not empty just because of edge + # direction (this is a conceptually undirected "PC runs machine" report). + # Each half joins a computer on one end and a machine on the other; the + # machine end supplies machine_number/vendor/model, the PC end hostname/ip. + half = """ SELECT eq.assetnumber AS machine_number, v.vendor AS vendor, @@ -477,17 +482,22 @@ def pc_relationships(): COALESCE(cpc.hostname, pc.assetnumber) AS hostname, c.ipaddress AS ip FROM assetrelationships ar - JOIN assets pc ON ar.sourceassetid = pc.assetid - JOIN computers cpc ON cpc.assetid = pc.assetid - JOIN assets eq ON ar.targetassetid = eq.assetid - JOIN machines eqx ON eqx.assetid = eq.assetid - LEFT JOIN communications c ON c.assetid = pc.assetid AND c.isprimary = 1 AND c.comtypeid = 1 + JOIN computers cpc ON cpc.assetid = ar.{pc_end} + JOIN assets pc ON pc.assetid = ar.{pc_end} + JOIN machines eqx ON eqx.assetid = ar.{machine_end} + JOIN assets eq ON eq.assetid = ar.{machine_end} + LEFT JOIN communications c ON c.assetid = pc.assetid AND c.isprimary = 1 LEFT JOIN models mo ON eqx.modelnumberid = mo.modelnumberid LEFT JOIN vendors v ON mo.vendorid = v.vendorid WHERE ar.isactive = 1 AND eq.assetnumber IS NOT NULL AND eq.assetnumber != '' - ORDER BY eq.assetnumber - """) + """ + sql = db.text( + half.format(pc_end='sourceassetid', machine_end='targetassetid') + + ' UNION ' + + half.format(pc_end='targetassetid', machine_end='sourceassetid') + + ' ORDER BY machine_number' + ) results = db.session.execute(sql).fetchall() data = [{ diff --git a/tests/test_core/test_pc_relationships.py b/tests/test_core/test_pc_relationships.py new file mode 100644 index 0000000..cbd7e57 --- /dev/null +++ b/tests/test_core/test_pc_relationships.py @@ -0,0 +1,50 @@ +"""Regression: /api/reports/pc-relationships must return PC<->machine links +regardless of edge direction. + +The import stores the general machinerelationships as machine(source) -> +PC(target); an earlier query only matched PC(source) -> machine(target), so on +prod (331 relationships) the report came back empty. The query is now +direction-agnostic. +""" + + +def _asset(db, assettype_name, plugin, table, assetnumber): + from shopdb.core.models import AssetType, Asset + at = AssetType.query.filter_by(assettype=assettype_name).first() + if not at: + at = AssetType(assettype=assettype_name, pluginname=plugin, tablename=table) + db.session.add(at) + db.session.flush() + asset = Asset(assettypeid=at.assettypeid, assetnumber=assetnumber) + db.session.add(asset) + db.session.flush() + return asset + + +def test_pc_relationships_matches_machine_to_pc_direction(client, db, auth_headers): + from shopdb.core.models import AssetRelationship, RelationshipType + from plugins.computers.models import Computer + from plugins.machines.models import Machine + + pc_asset = _asset(db, 'computer', 'computers', 'computers', 'TESTPC01') + eq_asset = _asset(db, 'machine', 'machines', 'machines', '4242') + db.session.add(Computer(assetid=pc_asset.assetid, hostname='testpc01')) + db.session.add(Machine(assetid=eq_asset.assetid)) + + rt = RelationshipType.query.filter_by(relationshiptype='controls').first() + if not rt: + rt = RelationshipType(relationshiptype='controls') + db.session.add(rt) + db.session.flush() + + # REVERSE of the old-only direction: machine is the source, PC the target. + db.session.add(AssetRelationship( + sourceassetid=eq_asset.assetid, targetassetid=pc_asset.assetid, + relationshiptypeid=rt.relationshiptypeid, isactive=True)) + db.session.commit() + + resp = client.get('/api/reports/pc-relationships', headers=auth_headers) + assert resp.status_code == 200 + rows = resp.get_json()['data']['data'] + assert any(r['machine_number'] == '4242' and r['hostname'] == 'testpc01' + for r in rows), rows