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 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
132 changes: 122 additions & 10 deletions frontend/src/ide/editor/lineage-tab/lineage-tab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,81 @@ const transformLineageData = (data) => {
return data;
};

function LineageTab({ nodeData }) {
// Find all ancestor and descendant node IDs for a given model
const getRelatedNodeIds = (allEdges, selectedLabel, allNodes) => {
Comment thread
tahierhussain marked this conversation as resolved.
Outdated
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;
};

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 #1677ff"
Comment thread
tahierhussain marked this conversation as resolved.
Outdated
: 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) ? "#1677ff" : undefined,
},
}));

return { nodes: styledNodes, edges: styledEdges };
};

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 +560,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 +595,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 +775,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 +1068,7 @@ function LineageTab({ nodeData }) {

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

export { LineageTab };
103 changes: 99 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 @@ -261,8 +261,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 +750,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 +2170,80 @@ function NoCodeModel({ nodeData }) {
);
};

// Find all ancestor and descendant node IDs for a given model
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;
};

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 #1677ff"
: 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) ? "#1677ff" : undefined,
},
}));

return { nodes: styledNodes, edges: styledEdges };
};

const getLineageData = (callSample = false) => {
if (!projectId) return;
setLineageData();
Comment thread
wicky-zipstack marked this conversation as resolved.
Expand All @@ -2174,8 +2259,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