Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 the model structure in sorted order
no_code_model_structure = []
for no_code_model_name in sorted_model_names:
Expand All @@ -103,6 +106,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, []),
}
)
model_structure: dict[str, Any] = {
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 }) {
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 };
};
31 changes: 27 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 @@ -69,6 +69,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 @@ -261,8 +262,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 @@ -740,6 +751,7 @@ function NoCodeModel({ nodeData }) {
setSeqEdges(layoutedEdges);
runTransformation(res?.data?.model_data);
setConfigApply(true);
setRefreshModels(true);
handleModalClose("ok");
})
.catch((error) => {
Expand Down Expand Up @@ -2159,6 +2171,7 @@ function NoCodeModel({ nodeData }) {
);
};

// Find all ancestor and descendant node IDs for a given model
const getLineageData = (callSample = false) => {
if (!projectId) return;
setLineageData();
Expand All @@ -2174,8 +2187,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