mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-13 15:40:05 +00:00
fix: correct node info field names
This commit is contained in:
1 parent
4c21b15e9b
commit
390264da80
2 files changed
+197
-10
No files matched your search
+21
-10
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user