Skip to content
Merged
4 changes: 4 additions & 0 deletions backend/backend/application/file_explorer/file_explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ def load_models(self, session: Session):
# Sort models by execution order (DAG order)
sorted_model_names = topological_sort_models(models_with_refs)

# Build lookup for references by model name
refs_by_name = {m["model_name"]: m["references"] for m in models_with_refs}

# Build a lookup from model name -> model object for status fields
model_lookup = {m.model_name: m for m in all_models}

Expand All @@ -106,6 +109,7 @@ def load_models(self, session: Session):
"key": f"{self.project_name}/models/no_code/{no_code_model_name}",
"is_folder": False,
"type": "NO_CODE_MODEL",
"references": refs_by_name.get(no_code_model_name, []),
"run_status": getattr(model, "run_status", None),
"failure_reason": getattr(model, "failure_reason", None),
"last_run_at": model.last_run_at.isoformat() if getattr(model, "last_run_at", None) else None,
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/ide/chat-ai/ChatAI.css
Original file line number Diff line number Diff line change
Expand Up @@ -466,12 +466,12 @@
inset-block-start: 50%;
}
.chat-ai-info-chip-error {
background-color: var(--ant-color-error-bg);
border-color: var(--ant-color-error-border);
background-color: var(--content-bg);
border-color: var(--border-color-3);
}
.chat-ai-info-chip-error .chat-ai-info-chip-icon,
.chat-ai-info-chip-error .chat-ai-info-chip-text {
color: var(--ant-color-error);
color: var(--error-color);
}
.chat-ai-manage-credits-link:hover {
color: var(--icons-color);
Expand Down
59 changes: 49 additions & 10 deletions frontend/src/ide/editor/lineage-tab/lineage-tab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { THEME } from "../../../common/constants.js";
import { SpinnerLoader } from "../../../widgets/spinner_loader/index.js";
import { useNotificationService } from "../../../service/notification-service.js";
import { Tech } from "../../../base/icons/index.js";
import { applyScopedStyles } from "../lineage-utils.js";

import "reactflow/dist/style.css";
import "./lineage-tab.css";
Expand Down Expand Up @@ -289,7 +290,7 @@ const transformLineageData = (data) => {
return data;
};

function LineageTab({ nodeData }) {
function LineageTab({ nodeData, selectedModelName }) {
Comment thread
wicky-zipstack marked this conversation as resolved.
const axios = useAxiosPrivate();
const { selectedOrgId } = orgStore();
const { projectId } = useProjectStore();
Expand Down Expand Up @@ -486,15 +487,32 @@ function LineageTab({ nodeData }) {
transformedData.edges,
layoutDirection
);
setNodes(layoutedNodes);
setEdges(layoutedEdges);
if (selectedModelName) {
const scoped = applyScopedStyles(
layoutedNodes,
layoutedEdges,
selectedModelName
);
setNodes(scoped.nodes);
setEdges(scoped.edges);
} else {
setNodes(layoutedNodes);
setEdges(layoutedEdges);
}
})
.catch((error) => {
console.error(error);
notify({ error });
setLineageData({});
});
}, [projectId, selectedOrgId, setNodes, setEdges, layoutDirection]);
}, [
projectId,
selectedOrgId,
setNodes,
setEdges,
layoutDirection,
selectedModelName,
]);

const handleToggleLayout = useCallback(() => {
const newDirection = layoutDirection === "TB" ? "LR" : "TB";
Expand All @@ -504,10 +522,20 @@ function LineageTab({ nodeData }) {
if (lineageData && lineageData.nodes && lineageData.edges) {
const { nodes: layoutedNodes, edges: layoutedEdges } =
getLayoutedElements(lineageData.nodes, lineageData.edges, newDirection);
setNodes(layoutedNodes);
setEdges(layoutedEdges);
if (selectedModelName) {
const scoped = applyScopedStyles(
layoutedNodes,
layoutedEdges,
selectedModelName
);
setNodes(scoped.nodes);
setEdges(scoped.edges);
} else {
setNodes(layoutedNodes);
setEdges(layoutedEdges);
}
}
}, [layoutDirection, lineageData, setNodes, setEdges]);
}, [layoutDirection, lineageData, setNodes, setEdges, selectedModelName]);

// Fetch sequence data for a model
const fetchSequenceData = useCallback(
Expand Down Expand Up @@ -674,15 +702,25 @@ function LineageTab({ nodeData }) {
transformedData.edges,
"TB"
);
setNodes(layoutedNodes);
setEdges(layoutedEdges);
if (selectedModelName) {
const scoped = applyScopedStyles(
layoutedNodes,
layoutedEdges,
selectedModelName
);
setNodes(scoped.nodes);
setEdges(scoped.edges);
} else {
setNodes(layoutedNodes);
setEdges(layoutedEdges);
}
})
.catch((error) => {
console.error(error);
notify({ error });
setLineageData({});
});
}, [projectId, selectedOrgId, setNodes, setEdges]);
}, [projectId, selectedOrgId, setNodes, setEdges, selectedModelName]);

if (!lineageData) {
return <SpinnerLoader />;
Expand Down Expand Up @@ -957,6 +995,7 @@ function LineageTab({ nodeData }) {

LineageTab.propTypes = {
nodeData: PropTypes.object,
selectedModelName: PropTypes.string,
};

export { LineageTab };
98 changes: 98 additions & 0 deletions frontend/src/ide/editor/lineage-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* Shared utility functions for lineage scoping.
* Used by both lineage-tab.jsx (standalone) and no-code-model.jsx (bottom section).
*/

/**
* Find all ancestor and descendant node IDs for a given model.
* @param {Array} allEdges - Array of { source, target } edge objects
* @param {string} selectedLabel - The label of the selected model
* @param {Array} allNodes - Array of node objects with data.originalLabel or data.label
* @return {Set|null} Set of related node IDs, or null if selected model not found
*/
export const getRelatedNodeIds = (allEdges, selectedLabel, allNodes) => {
const nodeByLabel = {};
allNodes.forEach((n) => {
nodeByLabel[n.data.originalLabel || n.data.label] = n.id;
});
const selectedId = nodeByLabel[selectedLabel];
if (!selectedId) return null;

const related = new Set([selectedId]);
const findAncestors = (id) => {
allEdges.forEach((e) => {
if (e.target === id && !related.has(e.source)) {
related.add(e.source);
findAncestors(e.source);
}
});
};
const findDescendants = (id) => {
allEdges.forEach((e) => {
if (e.source === id && !related.has(e.target)) {
related.add(e.target);
findDescendants(e.target);
}
});
};
findAncestors(selectedId);
findDescendants(selectedId);
return related;
};

/**
* Apply scoped styles to nodes and edges based on the selected model's lineage chain.
* Related nodes stay full opacity, unrelated nodes are faded.
* @param {Array} layoutedNodes - Array of positioned node objects
* @param {Array} layoutedEdges - Array of edge objects
* @param {string} selectedLabel - The label of the selected model
* @return {Object} { nodes, edges } with scoped styles applied
*/
export const applyScopedStyles = (
layoutedNodes,
layoutedEdges,
selectedLabel
) => {
const rawEdges = layoutedEdges.map((e) => ({
source: e.source,
target: e.target,
}));
const related = getRelatedNodeIds(rawEdges, selectedLabel, layoutedNodes);
if (!related) return { nodes: layoutedNodes, edges: layoutedEdges };

const styledNodes = layoutedNodes.map((node) => {
const nodeLabel = node.data.originalLabel || node.data.label;
const isSelected = nodeLabel === selectedLabel;
const isRelated = related.has(node.id);
return {
...node,
style: {
...node.style,
opacity: isRelated ? 1 : 0.25,
border: isSelected
? "2px dashed var(--lineage-selected-border)"
: node.style?.border || "1px solid var(--black)",
},
};
});

const relatedEdgeSet = new Set();
layoutedEdges.forEach((e) => {
if (related.has(e.source) && related.has(e.target)) {
relatedEdgeSet.add(e.id);
}
});

const styledEdges = layoutedEdges.map((edge) => ({
...edge,
style: {
...edge.style,
opacity: relatedEdgeSet.has(edge.id) ? 1 : 0.15,
stroke: relatedEdgeSet.has(edge.id)
? "var(--lineage-selected-border)"
: undefined,
},
}));

return { nodes: styledNodes, edges: styledEdges };
};
32 changes: 28 additions & 4 deletions frontend/src/ide/editor/no-code-model/no-code-model.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ import dagre from "dagre";
import { useAxiosPrivate } from "../../../service/axios-service.js";
import { NoCodeToolbar } from "../no-code-toolbar/no-code-toolbar.jsx";
import { NoCodeTopbar } from "../no-code-topbar/no-code-topbar.jsx";
import { applyScopedStyles } from "../lineage-utils.js";
import { ConfigureSourceDestination } from "../no-code-configuration/configure-source-destination.jsx";
import { ConfigureJoins } from "../no-code-configuration/configure-joins.jsx";
import { useProjectStore } from "../../../store/project-store.js";
Expand Down Expand Up @@ -308,8 +309,18 @@ function NoCodeModel({ nodeData }) {
if (lineageData?.nodes && lineageData?.edges) {
const { nodes: layoutedNodes, edges: layoutedEdges } =
getLayoutedElements(lineageData.nodes, lineageData.edges, newDirection);
setNodes(layoutedNodes);
setEdges(layoutedEdges);
if (modelName) {
const scoped = applyScopedStyles(
layoutedNodes,
layoutedEdges,
modelName
);
setNodes(scoped.nodes);
setEdges(scoped.edges);
} else {
setNodes(layoutedNodes);
setEdges(layoutedEdges);
}
}
};

Expand Down Expand Up @@ -1875,6 +1886,8 @@ function NoCodeModel({ nodeData }) {
axios(requestOptions)
.then(() => {
getSampleData(undefined, undefined, spec);
// Trigger explorer refresh after run completes so updated references
// (from set-model save) reflect in the dependency chain sort
setRefreshModels(true);
})
.catch((error) => {
Expand Down Expand Up @@ -2438,6 +2451,7 @@ function NoCodeModel({ nodeData }) {
);
};

// Find all ancestor and descendant node IDs for a given model
const getLineageData = (callSample = false) => {
if (!projectId) return;
setLineageData();
Comment thread
wicky-zipstack marked this conversation as resolved.
Expand All @@ -2453,8 +2467,18 @@ function NoCodeModel({ nodeData }) {
setLineageData(data);
const { nodes: layoutedNodes, edges: layoutedEdges } =
getLayoutedElements(data.nodes, data.edges, lineageLayoutDirection);
setNodes(layoutedNodes);
setEdges(layoutedEdges);
if (modelName) {
const scoped = applyScopedStyles(
layoutedNodes,
layoutedEdges,
modelName
);
setNodes(scoped.nodes);
setEdges(scoped.edges);
} else {
setNodes(layoutedNodes);
setEdges(layoutedEdges);
}
})
.catch((error) => {
console.error(error);
Expand Down
Loading
Loading