mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-29 04:26:20 +00:00
fix: restore error surfacing dropped by inlined mount-effect fetches
The set-state-in-effect refactor inlined each initial-fetch effect as a standalone `fetchInitial`, duplicating the logic of the existing reload/fetchOverview/fetchRegistry/loadVersions callbacks instead of reusing them (required, since eslint-plugin-react-hooks v7 flags calling an outside setState-touching function directly from an effect body, even through an async gap - verified via a local lint probe). The duplicates dropped the setError/flashMsg calls the originals had, so a failed initial page load in AlignmentsTab, KGOverviewTab, OntologyManager, and VersionsTab now failed silently instead of showing an error - a regression of the exact bug #767/#790 fixed for these same files. Also fixes LineageDiagram only clearing nodes/edges when the new activeId was falsy, leaving the previous lineage view's stale diagram on screen while switching directly between two ids.
This commit is contained in:
@@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`react-hooks/set-state-in-effect` cascading renders across 12 Explorer workspace files** (#769, #796) by @Sameer6305 and @KaifAhmad1
|
||||
- Replaced synchronous `setState` calls inside `useEffect` bodies with React's recommended "adjust state during render" pattern (`if (x !== prevX) { setPrevX(x); ...setState... }`) across `OntologyWorkspace`, `ManageWorkspace`, `LineageWorkspace`, and `GraphWorkspace`, and inlined async data-fetching effects with `ignore` flags to prevent race conditions and stale writes after unmount
|
||||
- Fixed a regression the inlining itself introduced: `AlignmentsTab.tsx`, `KGOverviewTab.tsx`, `OntologyManager.tsx`, and `VersionsTab.tsx` each duplicated their existing fetch callback (`reload` / `fetchOverview` / `fetchRegistry` / `loadVersions`+`loadProposals`) into a second, inline copy for the mount effect, and the copy silently dropped the `setError`/`flashMsg` calls the original had — re-introducing, on the very first page load, the exact error-swallowing behavior that #767/#790 had already fixed for these same files. The inline copies now mirror the original's error handling (including `207` partial-success messages) exactly
|
||||
- Fixed `LineageDiagram.tsx` only clearing the previously-rendered nodes/edges when the new `activeId` was falsy instead of on every id change, so switching directly between two lineage views briefly kept showing the *previous* view's stale diagram instead of clearing before the new fetch resolved
|
||||
- `GraphWorkspace.tsx` and `GraphLoadingOverlay.tsx` still have unrelated `react-hooks/set-state-in-effect` violations outside this PR's 12-file scope (confirmed via `npx eslint .`); left as follow-up work rather than expanding this PR further
|
||||
|
||||
- **Checkov flagged the knowledge-explorer Helm chart for using the default Kubernetes namespace** ([code scanning alert #779](https://github.com/semantica-agi/semantica/security/code-scanning/779), [#778](https://github.com/semantica-agi/semantica/security/code-scanning/778), [#777](https://github.com/semantica-agi/semantica/security/code-scanning/777), `CKV_K8S_21`) by @KaifAhmad1
|
||||
- `templates/service.yaml`, `templates/deployment.yaml`, and `templates/configmap.yaml` all already set `metadata.namespace` to `{{ .Release.Namespace }}`, which is only bound at `helm install`/`helm template` time; Checkov's helm framework renders the chart without a namespace override, so it always resolves to `default` and trips `CKV_K8S_21` even though the chart is namespace-agnostic by design
|
||||
- Added a `checkov.io/skip1: CKV_K8S_21` metadata annotation to each of the three files to suppress the scanner artifact false-positive properly in Helm templates, and documented the reasoning in `.checkov.yaml`
|
||||
|
||||
@@ -55,11 +55,9 @@ export function LineageDiagram() {
|
||||
const [prevActiveId, setPrevActiveId] = useState(activeId);
|
||||
if (activeId !== prevActiveId) {
|
||||
setPrevActiveId(activeId);
|
||||
if (!activeId) {
|
||||
setError("");
|
||||
setNodes([]);
|
||||
setEdges([]);
|
||||
}
|
||||
setError("");
|
||||
setNodes([]);
|
||||
setEdges([]);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -134,36 +134,45 @@ export function KGOverviewTab() {
|
||||
fetch("/api/graph/nodes?limit=500"),
|
||||
]);
|
||||
|
||||
if (!statsRes.ok) throw new Error(`Stats fetch failed (${statsRes.status})`);
|
||||
if (!nodesRes.ok) throw new Error(`Nodes fetch failed (${nodesRes.status})`);
|
||||
|
||||
const statsData: KGStats = await statsRes.json();
|
||||
if (!ignore) {
|
||||
if (statsRes.ok) {
|
||||
const statsData: KGStats = await statsRes.json();
|
||||
setStats(statsData);
|
||||
}
|
||||
|
||||
if (nodesRes.ok) {
|
||||
const nodesData: NodeListResponse = await nodesRes.json();
|
||||
const nodes = nodesData.nodes ?? [];
|
||||
setNodeTypeMap(buildTypeMap(nodes, "type"));
|
||||
|
||||
const edgesRes = await fetch("/api/graph/edges?limit=2000");
|
||||
if (edgesRes.ok) {
|
||||
const edgesData = await edgesRes.json();
|
||||
const edges: { source: string; target: string }[] = edgesData.edges ?? [];
|
||||
const degreeMap: Record<string, number> = {};
|
||||
for (const edge of edges) {
|
||||
degreeMap[edge.source] = (degreeMap[edge.source] ?? 0) + 1;
|
||||
degreeMap[edge.target] = (degreeMap[edge.target] ?? 0) + 1;
|
||||
}
|
||||
const sorted = nodes
|
||||
.map((n) => ({ node: n, neighborCount: degreeMap[n.id] ?? 0 }))
|
||||
.sort((a, b) => b.neighborCount - a.neighborCount)
|
||||
.slice(0, 10);
|
||||
setTopNodes(sorted);
|
||||
}
|
||||
setStats(statsData);
|
||||
if (statsRes.status === 207) {
|
||||
setError((statsData as { message?: string }).message || "Warning: Partial success loading stats.");
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
if (!ignore) setError("Failed to load graph overview. Ensure the server is running.");
|
||||
|
||||
const nodesData: NodeListResponse = await nodesRes.json();
|
||||
const nodes = nodesData.nodes ?? [];
|
||||
if (!ignore) {
|
||||
setNodeTypeMap(buildTypeMap(nodes, "type"));
|
||||
if (nodesRes.status === 207) {
|
||||
const nodesMessage = (nodesData as { message?: string }).message || "Warning: Partial success loading nodes.";
|
||||
setError((prev) => (prev ? `${prev} ${nodesMessage}` : nodesMessage));
|
||||
}
|
||||
}
|
||||
|
||||
// Simulate neighbor counts via edges fetch for top-N
|
||||
const edgesRes = await fetch("/api/graph/edges?limit=2000");
|
||||
if (edgesRes.ok) {
|
||||
const edgesData = await edgesRes.json();
|
||||
const edges: { source: string; target: string }[] = edgesData.edges ?? [];
|
||||
const degreeMap: Record<string, number> = {};
|
||||
for (const edge of edges) {
|
||||
degreeMap[edge.source] = (degreeMap[edge.source] ?? 0) + 1;
|
||||
degreeMap[edge.target] = (degreeMap[edge.target] ?? 0) + 1;
|
||||
}
|
||||
const sorted = nodes
|
||||
.map((n) => ({ node: n, neighborCount: degreeMap[n.id] ?? 0 }))
|
||||
.sort((a, b) => b.neighborCount - a.neighborCount)
|
||||
.slice(0, 10);
|
||||
if (!ignore) setTopNodes(sorted);
|
||||
}
|
||||
} catch (err) {
|
||||
if (!ignore) setError(err instanceof Error ? err.message : "Failed to load graph overview. Ensure the server is running.");
|
||||
} finally {
|
||||
if (!ignore) setLoading(false);
|
||||
}
|
||||
|
||||
@@ -77,17 +77,22 @@ export function AlignmentsTab() {
|
||||
loadOntologyRegistry(),
|
||||
loadAlignments(),
|
||||
]);
|
||||
if (ignore) return;
|
||||
|
||||
if (!ignore) {
|
||||
if (registryResult.status === "fulfilled") {
|
||||
setRegistry(registryResult.value);
|
||||
setSourceOntology((current) => current || registryResult.value[0]?.uri || "");
|
||||
setTargetOntology((current) => current || registryResult.value[1]?.uri || registryResult.value[0]?.uri || "");
|
||||
}
|
||||
if (alignmentResult.status === "fulfilled") {
|
||||
setAlignments(alignmentResult.value);
|
||||
}
|
||||
const errors: string[] = [];
|
||||
if (registryResult.status === "fulfilled") {
|
||||
setRegistry(registryResult.value);
|
||||
setSourceOntology((current) => current || registryResult.value[0]?.uri || "");
|
||||
setTargetOntology((current) => current || registryResult.value[1]?.uri || registryResult.value[0]?.uri || "");
|
||||
} else {
|
||||
errors.push(registryResult.reason instanceof Error ? registryResult.reason.message : "Failed to load ontology registry.");
|
||||
}
|
||||
if (alignmentResult.status === "fulfilled") {
|
||||
setAlignments(alignmentResult.value);
|
||||
} else {
|
||||
errors.push(alignmentResult.reason instanceof Error ? alignmentResult.reason.message : "Failed to load alignments.");
|
||||
}
|
||||
if (errors.length) setError(errors.join(" "));
|
||||
}
|
||||
void fetchInitial();
|
||||
return () => { ignore = true; };
|
||||
|
||||
@@ -286,21 +286,23 @@ export function OntologyManager() {
|
||||
const params = new URLSearchParams();
|
||||
if (searchQ) params.set("q", searchQ);
|
||||
const res = await fetch(`/api/ontology/registry?${params}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
if (!ignore) setEntries(data);
|
||||
} else {
|
||||
if (!ignore) setEntries([]);
|
||||
}
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const data = await res.json();
|
||||
if (ignore) return;
|
||||
setEntries(data);
|
||||
if (res.status === 207) flashMsg("err", data.message || "Warning: Partial success loading registry.");
|
||||
} catch {
|
||||
if (!ignore) setEntries([]);
|
||||
if (!ignore) {
|
||||
setEntries([]);
|
||||
flashMsg("err", "Failed to load ontology registry");
|
||||
}
|
||||
} finally {
|
||||
if (!ignore) setLoading(false);
|
||||
}
|
||||
}
|
||||
void fetchInitial();
|
||||
return () => { ignore = true; };
|
||||
}, [searchQ]);
|
||||
}, [searchQ, flashMsg]);
|
||||
|
||||
const handleToggle = useCallback(async (uri: string) => {
|
||||
try {
|
||||
|
||||
@@ -85,27 +85,36 @@ export function VersionsTab() {
|
||||
useEffect(() => {
|
||||
let ignore = false;
|
||||
async function fetchInitial() {
|
||||
if (!ignore) setError("");
|
||||
try {
|
||||
const [propRes] = await Promise.all([
|
||||
fetch("/api/ontology/proposals"),
|
||||
]);
|
||||
const propRes = await fetch("/api/ontology/proposals");
|
||||
if (propRes.ok) {
|
||||
const propData = await propRes.json();
|
||||
if (!ignore) setProposals(propData);
|
||||
if (!ignore) {
|
||||
setProposals(propData);
|
||||
if (propRes.status === 207) setError(propData.message || "Warning: Partial success loading proposals.");
|
||||
}
|
||||
} else if (!ignore) {
|
||||
setError(`Failed to load proposals (${propRes.status})`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to load proposals", e);
|
||||
} catch (err) {
|
||||
if (!ignore) setError(err instanceof Error ? err.message : "Failed to load proposals.");
|
||||
}
|
||||
|
||||
|
||||
if (!ontologyUri) return;
|
||||
try {
|
||||
const verRes = await fetch(`/api/ontology/versions/${encodeURIComponent(ontologyUri)}`);
|
||||
if (verRes.ok) {
|
||||
const verData = await verRes.json();
|
||||
if (!ignore) setVersions(verData);
|
||||
if (!ignore) {
|
||||
setVersions(verData);
|
||||
if (verRes.status === 207) setError(verData.message || "Warning: Partial success loading versions.");
|
||||
}
|
||||
} else if (!ignore) {
|
||||
setError((prev) => prev || `Failed to load versions (${verRes.status})`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to load versions", e);
|
||||
} catch (err) {
|
||||
if (!ignore) setError((prev) => prev || (err instanceof Error ? err.message : "Failed to load versions."));
|
||||
}
|
||||
}
|
||||
void fetchInitial();
|
||||
|
||||
Reference in New Issue
Block a user