Skip to content

Commit 4c66a34

Browse files
fix: resolve all 14 remaining SonarQube code smells
Python S3776 (6): extract helpers in twin.py and flows.py TypeScript S3776 (6): extract components in views + App.tsx TypeScript misc (2): remove void operator, remove role=button
1 parent 13dee70 commit 4c66a34

File tree

7 files changed

+772
-591
lines changed

7 files changed

+772
-591
lines changed

apps/api/app/routes/twin.py

Lines changed: 126 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1768,6 +1768,20 @@ def _build_community_points(
17681768
return points
17691769

17701770

1771+
def _community_graph_source_warnings(graph_source: str) -> list[str]:
1772+
"""Convert graph source into human-readable warnings."""
1773+
warnings: list[str] = []
1774+
if graph_source == "connectivity_recovery":
1775+
warnings.append(
1776+
"Recovered community graph from architecture/UI/flow links because symbol graph was sparse."
1777+
)
1778+
if graph_source == "knowledge_fallback":
1779+
warnings.append(
1780+
"No symbol dependency graph found. Using knowledge graph communities as fallback."
1781+
)
1782+
return warnings
1783+
1784+
17711785
async def _build_structural_community_points(
17721786
db: Any,
17731787
*,
@@ -1778,16 +1792,8 @@ async def _build_structural_community_points(
17781792
page: int,
17791793
limit: int,
17801794
) -> tuple[list[dict[str, Any]], list[str]]:
1781-
warnings: list[str] = []
17821795
graph_nodes, graph_edges, graph_source = await _load_community_graph(db, collection_id)
1783-
if graph_source == "connectivity_recovery":
1784-
warnings.append(
1785-
"Recovered community graph from architecture/UI/flow links because symbol graph was sparse."
1786-
)
1787-
if graph_source == "knowledge_fallback":
1788-
warnings.append(
1789-
"No symbol dependency graph found. Using knowledge graph communities as fallback."
1790-
)
1796+
warnings = _community_graph_source_warnings(graph_source)
17911797

17921798
if include_node_kinds:
17931799
graph_nodes = [node for node in graph_nodes if node.kind in include_node_kinds]
@@ -3999,6 +4005,55 @@ def _graphrag_status(
39994005
return {"status": "ready", "reason": "ok"}
40004006

40014007

4008+
def _build_graphrag_node_query(
4009+
collection_uuid: uuid.UUID,
4010+
*,
4011+
include_node_kinds: set[KnowledgeNodeKind] | None,
4012+
exclude_node_kinds: set[KnowledgeNodeKind] | None,
4013+
community_id: str | None,
4014+
communities: dict[str, dict[str, Any]],
4015+
) -> Any | None:
4016+
"""Build the KnowledgeNode query for graphrag. Returns None if the community is empty."""
4017+
node_query = select(KnowledgeNode).where(KnowledgeNode.collection_id == collection_uuid)
4018+
if include_node_kinds:
4019+
node_query = node_query.where(KnowledgeNode.kind.in_(include_node_kinds))
4020+
if exclude_node_kinds:
4021+
node_query = node_query.where(~KnowledgeNode.kind.in_(exclude_node_kinds))
4022+
if community_id:
4023+
member_ids = communities[community_id]["member_node_ids"]
4024+
if not member_ids:
4025+
return None
4026+
node_query = node_query.where(KnowledgeNode.id.in_(member_ids))
4027+
return node_query
4028+
4029+
4030+
def _empty_graphrag_response(
4031+
collection_uuid: uuid.UUID,
4032+
scenario: Any,
4033+
community_mode: str,
4034+
community_id: str | None,
4035+
page: int,
4036+
limit: int,
4037+
) -> dict[str, Any]:
4038+
"""Return an empty graphrag response for an empty community."""
4039+
return {
4040+
"collection_id": str(collection_uuid),
4041+
"scenario": _serialize_scenario(scenario),
4042+
"projection": "graphrag",
4043+
"entity_level": "knowledge_node",
4044+
"community_mode": community_mode,
4045+
"community_id": community_id,
4046+
"status": {"status": "unavailable", "reason": "no_knowledge_graph"},
4047+
"graph": {
4048+
"nodes": [],
4049+
"edges": [],
4050+
"page": page,
4051+
"limit": limit,
4052+
"total_nodes": 0,
4053+
},
4054+
}
4055+
4056+
40024057
@router.get(
40034058
"/collections/{collection_id}/views/graphrag",
40044059
responses={
@@ -4050,32 +4105,22 @@ async def graphrag_view(
40504105
if resolved_community_id and resolved_community_id not in communities:
40514106
raise HTTPException(status_code=404, detail="Community not found")
40524107

4053-
node_query = select(KnowledgeNode).where(KnowledgeNode.collection_id == collection_uuid)
4054-
if include_node_kinds:
4055-
node_query = node_query.where(KnowledgeNode.kind.in_(include_node_kinds))
4056-
if exclude_node_kinds:
4057-
node_query = node_query.where(~KnowledgeNode.kind.in_(exclude_node_kinds))
4058-
if resolved_community_id:
4059-
member_ids = communities[resolved_community_id]["member_node_ids"]
4060-
if not member_ids:
4061-
status = {"status": "unavailable", "reason": "no_knowledge_graph"}
4062-
return {
4063-
"collection_id": str(collection_uuid),
4064-
"scenario": _serialize_scenario(scenario),
4065-
"projection": "graphrag",
4066-
"entity_level": "knowledge_node",
4067-
"community_mode": resolved_community_mode,
4068-
"community_id": resolved_community_id,
4069-
"status": status,
4070-
"graph": {
4071-
"nodes": [],
4072-
"edges": [],
4073-
"page": page,
4074-
"limit": limit,
4075-
"total_nodes": 0,
4076-
},
4077-
}
4078-
node_query = node_query.where(KnowledgeNode.id.in_(member_ids))
4108+
node_query = _build_graphrag_node_query(
4109+
collection_uuid,
4110+
include_node_kinds=include_node_kinds,
4111+
exclude_node_kinds=exclude_node_kinds,
4112+
community_id=resolved_community_id,
4113+
communities=communities,
4114+
)
4115+
if node_query is None:
4116+
return _empty_graphrag_response(
4117+
collection_uuid,
4118+
scenario,
4119+
resolved_community_mode,
4120+
resolved_community_id,
4121+
page,
4122+
limit,
4123+
)
40794124

40804125
total_nodes = (
40814126
await db.execute(select(func.count()).select_from(node_query.subquery()))
@@ -5470,6 +5515,28 @@ async def _export_mermaid_c4_format(
54705515
}
54715516

54725517

5518+
async def _export_graph_format(
5519+
db: Any,
5520+
scenario: Any,
5521+
projection: GraphProjection,
5522+
entity_level: str | None,
5523+
*,
5524+
symbol_exporter: Any,
5525+
graph_exporter: Any,
5526+
) -> str:
5527+
"""Export using the symbol exporter for CODE_SYMBOL, otherwise build graph and use graph exporter."""
5528+
if projection == GraphProjection.CODE_SYMBOL:
5529+
return await symbol_exporter(db, scenario.id) # type: ignore[no-any-return]
5530+
graph = await get_full_scenario_graph(
5531+
session=db,
5532+
scenario_id=scenario.id,
5533+
layer=None,
5534+
projection=projection,
5535+
entity_level=entity_level,
5536+
)
5537+
return graph_exporter(scenario.id, graph) # type: ignore[no-any-return]
5538+
5539+
54735540
async def _generate_export_content(
54745541
db: Any,
54755542
scenario: Any,
@@ -5478,17 +5545,14 @@ async def _generate_export_content(
54785545
) -> tuple[dict | None, str, Any, str, GraphProjection]:
54795546
"""Dispatch to the correct export format and return (early_return, content, kind, name, projection)."""
54805547
if body.format == "lpg_jsonl":
5481-
if projection == GraphProjection.CODE_SYMBOL:
5482-
content = await export_lpg_jsonl(db, scenario.id)
5483-
else:
5484-
graph = await get_full_scenario_graph(
5485-
session=db,
5486-
scenario_id=scenario.id,
5487-
layer=None,
5488-
projection=projection,
5489-
entity_level=body.entity_level,
5490-
)
5491-
content = export_lpg_jsonl_from_graph(scenario.id, graph)
5548+
content = await _export_graph_format(
5549+
db,
5550+
scenario,
5551+
projection,
5552+
body.entity_level,
5553+
symbol_exporter=export_lpg_jsonl,
5554+
graph_exporter=export_lpg_jsonl_from_graph,
5555+
)
54925556
return (
54935557
None,
54945558
content,
@@ -5507,30 +5571,24 @@ async def _generate_export_content(
55075571
)
55085572
return None, content, KnowledgeArtifactKind.CC_JSON, f"{scenario.name}.cc.json", projection
55095573
if body.format == "cx2":
5510-
if projection == GraphProjection.CODE_SYMBOL:
5511-
content = await export_cx2(db, scenario.id)
5512-
else:
5513-
graph = await get_full_scenario_graph(
5514-
session=db,
5515-
scenario_id=scenario.id,
5516-
layer=None,
5517-
projection=projection,
5518-
entity_level=body.entity_level,
5519-
)
5520-
content = export_cx2_from_graph(scenario.id, graph)
5574+
content = await _export_graph_format(
5575+
db,
5576+
scenario,
5577+
projection,
5578+
body.entity_level,
5579+
symbol_exporter=export_cx2,
5580+
graph_exporter=export_cx2_from_graph,
5581+
)
55215582
return None, content, KnowledgeArtifactKind.CX2, f"{scenario.name}.cx2.json", projection
55225583
if body.format == "jgf":
5523-
if projection == GraphProjection.CODE_SYMBOL:
5524-
content = await export_jgf(db, scenario.id)
5525-
else:
5526-
graph = await get_full_scenario_graph(
5527-
session=db,
5528-
scenario_id=scenario.id,
5529-
layer=None,
5530-
projection=projection,
5531-
entity_level=body.entity_level,
5532-
)
5533-
content = export_jgf_from_graph(scenario.id, graph)
5584+
content = await _export_graph_format(
5585+
db,
5586+
scenario,
5587+
projection,
5588+
body.entity_level,
5589+
symbol_exporter=export_jgf,
5590+
graph_exporter=export_jgf_from_graph,
5591+
)
55345592
return None, content, KnowledgeArtifactKind.JGF, f"{scenario.name}.jgf.json", projection
55355593
if body.format == "twin_manifest":
55365594
content = await export_twin_manifest(db, scenario.id)

apps/web/src/App.tsx

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,19 @@ interface PrefectFlowRuns {
236236
error?: string
237237
}
238238

239+
function SyncStatusIndicator({ syncStatus }: Readonly<{ syncStatus: string }>) {
240+
if (syncStatus === 'success') return <></>
241+
if (syncStatus === 'syncing') return <></>
242+
if (syncStatus === 'failed') return <></>
243+
return <></>
244+
}
245+
246+
function SourceCountLabel({ sourceCount }: Readonly<{ sourceCount: number }>) {
247+
if (sourceCount === 0) return <>No sources</>
248+
const plural = sourceCount === 1 ? '' : 's'
249+
return <>{sourceCount} source{plural}</>
250+
}
251+
239252
function MemberChip({ member, onRemove }: Readonly<{ member: { user_id: string; github_login: string; is_owner: boolean }; onRemove: (id: string) => void }>) {
240253
return (
241254
<span className="member-chip">
@@ -512,8 +525,7 @@ function App() {
512525
const [editCollectionLoading, setEditCollectionLoading] = useState(false)
513526

514527
// Sources state
515-
const [sources, setSources] = useState<Source[]>([])
516-
void sources // used via setSources; value read by child components via collectionSources
528+
const [, setSources] = useState<Source[]>([])
517529
const [newSourceType, setNewSourceType] = useState<'github' | 'web'>('github')
518530
const [newSourceUrl, setNewSourceUrl] = useState('')
519531
const [newSourceEnabled, setNewSourceEnabled] = useState(true)
@@ -1862,7 +1874,7 @@ function App() {
18621874
return (
18631875
<div key={collection.id} className={`collection-row ${isExpanded ? 'expanded' : ''}`}>
18641876
{/* Collection Header Row */}
1865-
<div className="collection-header-row" role="button" tabIndex={0} aria-label={`Toggle collection ${collection.name}`} onClick={(e) => { if (!(e.target as HTMLElement).closest('form, [role="toolbar"]')) { handleToggleExpand(collection) } }} onKeyDown={e => { if (e.key === 'Enter') { handleToggleExpand(collection) } }}>
1877+
<div className="collection-header-row" tabIndex={0} aria-label={`Toggle collection ${collection.name}`} onClick={(e) => { if (!(e.target as HTMLElement).closest('form, [role="toolbar"]')) { handleToggleExpand(collection) } }} onKeyDown={e => { if (e.key === 'Enter') { handleToggleExpand(collection) } }}>
18661878
<button className="expand-toggle" aria-label={isExpanded ? 'Collapse' : 'Expand'}>
18671879
{isExpanded ? '▼' : '▶'}
18681880
</button>
@@ -1901,18 +1913,11 @@ function App() {
19011913
</div>
19021914

19031915
<div className="collection-stats">
1904-
<span className="stat">{(() => {
1905-
if (sourceCount === 0) return 'No sources'
1906-
const plural = sourceCount === 1 ? '' : 's'
1907-
return `${sourceCount} source${plural}`
1908-
})()}</span>
1916+
<span className="stat"><SourceCountLabel sourceCount={sourceCount} /></span>
19091917
<span className="stat">{docCount > 0 ? `${docCount} docs` : ''}</span>
19101918
{sourceCount > 0 && (
19111919
<span className={`sync-status status-${syncStatus}`}>
1912-
{syncStatus === 'success' && '●'}
1913-
{syncStatus === 'syncing' && '◐'}
1914-
{syncStatus === 'failed' && '●'}
1915-
{syncStatus === 'never' && '○'}
1920+
<SyncStatusIndicator syncStatus={syncStatus} />
19161921
</span>
19171922
)}
19181923
</div>

0 commit comments

Comments
 (0)