fix: correct node info field names

(cherry picked from commit 390264da80)
This commit is contained in:
himanshupatro-334 authored and safishamsi committed 2026-09-29 13:49:39 +01:00
1 parent 9fd5aadfd8
commit 3394d10ccf
2 files changed
+197 -10

No files matched your search

+21 -10
View File
@@ -157,8 +157,9 @@ const nodesDS = new vis.DataSet(RAW_NODES.map((n, i) => ({{
// graphs. `i` is the map index — physics still settles small graphs identically.
x: 30 * Math.sqrt(i) * Math.cos(i * 2.4),
y: 30 * Math.sqrt(i) * Math.sin(i * 2.4),
_community: n.community, _community_name: n.community_name,
_source_file: n.source_file, _file_type: n.file_type, _degree: n.degree,
community: n.community, community_name: n.community_name,
source_file: n.source_file, file_type: n.file_type, degree: n.degree,
member_count: n.member_count,
}})));
const edgesDS = new vis.DataSet(RAW_EDGES.map((e, i) => ({{
@@ -210,14 +211,22 @@ function showInfo(nodeId) {{
const color = nb ? nb.color.background : '#555';
return `<span class="neighbor-link" style="border-left-color:${{esc(color)}}" data-nid="${{esc(nid)}}">${{esc(nb ? nb.label : nid)}}</span>`;
}}).join('');
document.getElementById('info-content').innerHTML = `
<div class="field"><b>${{esc(n.label)}}</b></div>
<div class="field">Type: ${{esc(n._file_type || 'unknown')}}</div>
<div class="field">Community: ${{esc(n._community_name)}}</div>
<div class="field">Source: ${{esc(n._source_file || '-')}}</div>
<div class="field">Degree: ${{n._degree}}</div>
${{neighborIds.length ? `<div class="field" style="margin-top:8px;color:#aaa;font-size:11px">Neighbors (${{neighborIds.length}})</div><div id="neighbors-list">${{neighborItems}}</div>` : ''}}
`;
const isMetaNode = !n.file_type && !n.source_file;
let html = `<div class="field"><b>${{esc(n.label)}}</b></div>`;
if (!isMetaNode) {{
html += `<div class="field">Type: ${{esc(n.file_type || 'unknown')}}</div>`;
}}
html += `<div class="field">Community: ${{esc(n.community_name || '')}}</div>`;
if (!isMetaNode) {{
html += `<div class="field">Source: ${{esc(n.source_file || '-')}}</div>`;
}} else if (n.member_count !== undefined && n.member_count !== null) {{
html += `<div class="field">Members: ${{esc(n.member_count)}}</div>`;
}}
html += `<div class="field">Degree: ${{n.degree}}</div>`;
if (neighborIds.length) {{
html += `<div class="field" style="margin-top:8px;color:#aaa;font-size:11px">Neighbors (${{neighborIds.length}})</div><div id="neighbors-list">${{neighborItems}}</div>`;
}}
document.getElementById('info-content').innerHTML = html;
}}
function focusNode(nodeId) {{
@@ -541,6 +550,8 @@ def to_html(
"file_type": data.get("file_type", ""),
"degree": deg,
}
if member_counts:
node["member_count"] = member_counts.get(cid, len(communities.get(cid, [])))
# Conditional learning fields — only present for annotated nodes, so
# un-annotated output keeps the exact pre-feature node dict shape.
entry = learning_overlay.get(str(node_id)) if learning_overlay else None
+176
View File
@@ -1197,3 +1197,179 @@ def test_to_html_spiral_seed_uses_a_real_map_index():
"spiral seed references `i` but the node map has no index param "
"-> ReferenceError: i is not defined (#3699)"
)
def test_to_html_canonical_node_schema_mapping(tmp_path):
"""#3914: nodesDS and showInfo must use canonical un-prefixed field names
matching RAW_NODES and Graphify's node schema (file_type, source_file,
community_name, degree), eliminating the underscore-prefixed mapping."""
import networkx as nx
G = nx.Graph()
G.add_node("n_sample", label="SampleNode", file_type="code", source_file="src/sample.py")
out = tmp_path / "graph.html"
to_html(G, {0: ["n_sample"]}, str(out), community_labels={0: "Core"})
content = out.read_text(encoding="utf-8")
# RAW_NODES carries canonical un-prefixed fields
nodes = {n["id"]: n for n in _vis_nodes_from_html(content)}
assert nodes["n_sample"]["file_type"] == "code"
assert nodes["n_sample"]["source_file"] == "src/sample.py"
assert nodes["n_sample"]["community_name"] == "Core"
assert "degree" in nodes["n_sample"]
# nodesDS dataset mapping must preserve canonical field names
assert "file_type: n.file_type" in content
assert "source_file: n.source_file" in content
assert "community_name: n.community_name" in content
assert "degree: n.degree" in content
# nodesDS must not contain old underscore-prefixed mappings
assert "_file_type: n.file_type" not in content
assert "_source_file: n.source_file" not in content
assert "_community_name: n.community_name" not in content
assert "_degree: n.degree" not in content
assert "_community: n.community" not in content
# showInfo must consume canonical fields directly
assert "n.file_type" in content
assert "n.source_file" in content
assert "n.community_name" in content
assert "n.degree" in content
assert "n._file_type" not in content
assert "n._source_file" not in content
assert "n._community_name" not in content
assert "n._degree" not in content
def _run_node_info_harness(html_content: str, target_node_id: str) -> str:
"""Helper to execute graph.html's client script in Node.js and return #info-content innerHTML."""
import re
import shutil
import subprocess
node = shutil.which("node")
if node is None:
import pytest
pytest.skip("node not available")
m = re.search(r"<script>(.*?)</script>\s*<script>", html_content, re.DOTALL)
assert m, "vis script block not found in html"
script = m.group(1)
harness = f"""
const mockElem = () => ({{
innerHTML: '',
style: {{}},
appendChild: () => {{}},
addEventListener: () => {{}},
classList: {{ add: () => {{}}, remove: () => {{}} }},
prepend: () => {{}},
}});
const doc = {{
elements: {{
'info-content': mockElem(),
'graph': mockElem(),
'search': mockElem(),
'search-results': mockElem(),
'select-all-cb': Object.assign(mockElem(), {{ checked: true }}),
'legend': mockElem()
}},
getElementById(id) {{
return this.elements[id] || (this.elements[id] = mockElem());
}},
createElement() {{ return mockElem(); }},
addEventListener() {{}},
querySelectorAll() {{ return []; }}
}};
class MockDataSet {{
constructor(items) {{
this.map = new Map();
items.forEach(it => this.map.set(it.id, it));
}}
get(id) {{ return this.map.get(id); }}
update() {{}}
}}
const vis = {{
DataSet: MockDataSet,
Network: class {{
constructor() {{}}
once() {{}}
on() {{}}
getConnectedNodes() {{ return []; }}
focus() {{}}
selectNodes() {{}}
}}
}};
const document = doc;
const window = {{}};
{script}
showInfo('{target_node_id}');
console.log('OUTPUT:' + doc.getElementById('info-content').innerHTML);
"""
proc = subprocess.run([node, "-e", harness], capture_output=True, text=True, timeout=30)
assert proc.returncode == 0, f"Node script failed ({proc.returncode}): {proc.stderr}"
m_out = re.search(r"OUTPUT:(.*)", proc.stdout)
assert m_out, f"OUTPUT not found in stdout: {proc.stdout}"
return m_out.group(1)
def test_to_html_show_info_runtime_renders_node_fields(tmp_path):
"""#3914: Execute showInfo in Node.js for a normal node.
Type: code and Source: src/sample.py must be rendered into info-content,
not Type: unknown or Source: -."""
import networkx as nx
G = nx.Graph()
G.add_node("n_sample", label="SampleNode", file_type="code", source_file="src/sample.py")
G.add_node("n_other", label="OtherNode", file_type="document", source_file="doc/readme.md")
G.add_edge("n_sample", "n_other", relation="references", confidence="EXTRACTED")
out = tmp_path / "graph.html"
to_html(G, {0: ["n_sample", "n_other"]}, str(out), community_labels={0: "Core"})
content = out.read_text(encoding="utf-8")
info_html = _run_node_info_harness(content, "n_sample")
assert '<div class="field"><b>SampleNode</b></div>' in info_html
assert '<div class="field">Type: code</div>' in info_html
assert '<div class="field">Community: Core</div>' in info_html
assert '<div class="field">Source: src/sample.py</div>' in info_html
assert '<div class="field">Degree: 1</div>' in info_html
assert "Type: unknown" not in info_html
assert "Source: -" not in info_html
def test_to_html_aggregated_community_nodes_runtime(tmp_path):
"""#3914: Execute showInfo in Node.js for an aggregated community node.
Community meta-nodes have no file_type/source_file and must not display
misleading Type: unknown or Source: -; they must display community name
and member count."""
import networkx as nx
meta = nx.Graph()
meta.add_node("0", label="Core Systems")
meta.add_node("1", label="UI Layer")
meta.add_edge("0", "1", relation="cross-community", confidence="AGGREGATED")
out = tmp_path / "graph.html"
to_html(
meta,
{0: ["0"], 1: ["1"]},
str(out),
community_labels={0: "Core Systems", 1: "UI Layer"},
member_counts={0: 25, 1: 10},
)
content = out.read_text(encoding="utf-8")
nodes = _vis_nodes_from_html(content)
assert any(n.get("member_count") == 25 for n in nodes)
info_html = _run_node_info_harness(content, "0")
assert '<div class="field"><b>Core Systems</b></div>' in info_html
assert '<div class="field">Community: Core Systems</div>' in info_html
assert '<div class="field">Members: 25</div>' in info_html
assert '<div class="field">Degree: 1</div>' in info_html
assert "Type: unknown" not in info_html
assert "Source: -" not in info_html