Skip to content

Commit

Permalink
Update diff tree construction by using "parent" value of DiffTree que…
Browse files Browse the repository at this point in the history
…ry (#4199)
  • Loading branch information
bilalabbad authored Aug 29, 2024
1 parent 0360739 commit a96adf2
Show file tree
Hide file tree
Showing 11 changed files with 1,058 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { gql } from "@apollo/client";

export const getProposedChangesDiffTree = gql`
query GET_PROPOSED_CHANGES_DIFF_TREE($branch: String, $filters: DiffTreeQueryFilters) {
DiffTree(branch: $branch, filters: $filters, include_parents: false) {
DiffTree(branch: $branch, filters: $filters, include_parents: true) {
nodes {
uuid
relationships {
Expand Down
5 changes: 3 additions & 2 deletions frontend/app/src/screens/diff/diff-badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Icon } from "@iconify-icon/react";

export interface DiffBadgeProps extends BadgeProps {
hasConflicts?: boolean;
icon?: string;
}

export type BadgeType =
Expand All @@ -19,7 +20,7 @@ export const BadgeUnchanged = ({
children,
hasConflicts = false,
...props
}: DiffBadgeProps & { icon?: string }) => {
}: DiffBadgeProps) => {
return (
<Badge
className={classNames(
Expand All @@ -29,7 +30,7 @@ export const BadgeUnchanged = ({
className
)}
{...props}>
<Icon icon={icon ?? "mdi:check"} className="text-xs" />
<Icon icon={icon ?? "mdi:dot-outline"} className="text-xs" />
{(children || children === 0) && <span className="font-medium text-xs">{children}</span>}
{hasConflicts && <BadgeConflict />}
</Badge>
Expand Down
191 changes: 116 additions & 75 deletions frontend/app/src/screens/diff/diff-tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,79 +20,9 @@ export default function DiffTree({ nodes, loading, emptyMessage, ...props }: Dif
const navigate = useNavigate();

useEffect(() => {
let kindToUUIDsMap: Record<string, Array<string>> = {};

const formattedNodes: TreeProps["data"] = nodes.flatMap((node: DiffNode) => {
const currentUUIDs = kindToUUIDsMap[node.kind];
kindToUUIDsMap[node.kind] = currentUUIDs ? [...currentUUIDs, node.uuid] : [node.uuid];

const newNode = {
id: node.uuid,
name: node.label,
parent: node.kind,
children: [] as string[],
isBranch: node.relationships.length > 0,
metadata: {
uuid: node.uuid,
status: node.status,
containsConflicts: node.contains_conflict,
},
};

const nodesFromRelationships = node.relationships.flatMap((relationship) => {
const relationshipNode = {
id: relationship?.name + newNode.id,
name: relationship?.label,
parent: newNode.id,
isBranch: !!relationship?.elements?.length,
children: [] as string[],
metadata: {
uuid: node.uuid,
containsConflicts: relationship.contains_conflict,
},
};

newNode.children.push(relationshipNode.id);

const relationshipChildNodes =
relationship?.elements?.map((element) => {
const child = {
id: newNode.id + element.peer_label + element?.peer_id,
name: element.peer_label,
parent: relationshipNode.id,
isBranch: false,
children: [],
metadata: {
uuid: element?.peer_id,
status: nodes.find(({ uuid }) => uuid === element.peer_id)?.status,
containsConflicts: element.contains_conflict,
},
};
relationshipNode.children.push(child.id);

return child;
}) ?? [];

return [relationshipNode, ...relationshipChildNodes];
});

return [newNode, ...nodesFromRelationships];
});

const parents = Object.entries(kindToUUIDsMap).map(([kind, uuids]) => {
return {
id: kind,
name: kind,
parent: TREE_ROOT_ID,
children: uuids,
isBranch: true,
metadata: {
kind,
},
};
});
setTreeData(addItemsToTree(EMPTY_TREE, [...parents, ...formattedNodes]));
}, [nodes.length]);
const formattedNodes = formatDiffNodesToDiffTree(nodes);
setTreeData(addItemsToTree(EMPTY_TREE, generateRootCategoryNodeForDiffTree(formattedNodes)));
}, [nodes]);

if (treeData.length <= 1) return <NoDataFound message={emptyMessage ?? "No data to display."} />;

Expand Down Expand Up @@ -120,7 +50,9 @@ const DiffTreeItem = ({ element }: TreeItemProps) => {
const diffNode = element.metadata as DiffNode | undefined;
const { schema } = useSchema(diffNode?.kind);

if (schema) {
// On diff tree, root tree item represents the model schema's name,
// providing a clear visual representation of the schema in the tree structure.
if (schema && element.parent === TREE_ROOT_ID) {
return (
<div className={"flex items-center gap-2 text-gray-800"} data-testid="hierarchical-tree-item">
<Icon icon={schema.icon as string} />
Expand All @@ -138,10 +70,119 @@ const DiffTreeItem = ({ element }: TreeItemProps) => {
<DiffBadge
status={element.metadata?.status as string}
hasConflicts={!!element.metadata?.containsConflicts}
icon
size="icon"
icon={schema?.icon ?? undefined}
/>

<span className="whitespace-nowrap">{element.name}</span>
</a>
);
};

export const formatDiffNodesToDiffTree = (nodes: Array<DiffNode>) => {
return nodes.reduce((acc, node) => {
const newNode = {
id: node.uuid,
name: node.label,
parent: TREE_ROOT_ID as string,
children: acc.filter(({ parent }) => parent === node.uuid).map(({ id }) => id),
metadata: {
kind: node.kind, // for icon on tree item
uuid: node.uuid, // for url
status: node.status, // for icon color
containsConflicts: node.contains_conflict, // for icon conflicts
},
};

if (node.parent) {
const { uuid: parentUUID, relationship_name: parentRelationshipName } = node.parent;

const parentNodeId = parentUUID + parentRelationshipName;
const newNodeWithParent = {
...newNode,
parent: parentNodeId,
};

const existingParentOfNewNode = acc.find(({ id }) => id === parentNodeId);
if (existingParentOfNewNode) {
return acc
.map((accNode) => {
if (accNode.id === parentNodeId) {
return {
...accNode,
children: [...new Set(accNode.children.concat(newNodeWithParent.id))],
};
}

return accNode;
})
.concat(newNodeWithParent);
}

const newParentNode = {
id: parentNodeId,
name: parentRelationshipName ?? "",
parent: parentUUID,
children: [newNode.id],
metadata: {
kind: node.parent.kind, // for icon on tree item
},
};

return acc
.map((accNode) => {
if (accNode.id === parentUUID) {
return {
...accNode,
children: [...new Set(accNode.children.concat(newParentNode.id))],
};
}

return accNode;
})
.concat(newParentNode, newNodeWithParent);
}

return [...acc, newNode];
}, [] as TreeProps["data"]);
};

export const generateRootCategoryNodeForDiffTree = (
diffTreeNodes: TreeProps["data"]
): TreeProps["data"] => {
return diffTreeNodes.reduce((acc, node) => {
const nodeKind = node.metadata?.kind as string | undefined;
if (node.parent !== TREE_ROOT_ID || !nodeKind) return [...acc, node];

const nodeUpdated = {
...node,
parent: nodeKind,
};

const existingRootCategoryNode = acc.find(({ id }) => id === nodeKind);

if (existingRootCategoryNode) {
const rootCategoryNodeUpdated = {
...existingRootCategoryNode,
children: [...existingRootCategoryNode.children, node.id],
};

return acc
.map((item) => (item.id === existingRootCategoryNode.id ? rootCategoryNodeUpdated : item))
.concat(nodeUpdated);
} else {
const newRootCategoryNode = {
id: nodeKind,
name: nodeKind,
parent: TREE_ROOT_ID,
children: [node.id],
isBranch: true,
metadata: {
kind: nodeKind,
},
};

return [...acc, newRootCategoryNode, nodeUpdated];
}
}, [] as TreeProps["data"]);
};
18 changes: 10 additions & 8 deletions frontend/app/src/screens/diff/node-diff/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,16 @@ export const NodeDiff = ({ filters }: NodeDiffProps) => {

{!loading &&
!!nodes.length &&
nodes.map((node) => (
<DiffNode
key={node.uuid}
node={node}
sourceBranch={data?.DiffTree?.base_branch}
destinationBranch={data?.DiffTree?.diff_branch}
/>
))}
nodes
.filter(({ status }) => status !== "UNCHANGED")
.map((node) => (
<DiffNode
key={node.uuid}
node={node}
sourceBranch={data?.DiffTree?.base_branch}
destinationBranch={data?.DiffTree?.diff_branch}
/>
))}
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const DiffNodeRelationshipElement = ({ element, status }: DiffNodeElement
title={
<div className="flex items-center justify-between pl-4 pr-2">
<div className="flex gap-1 py-2">
<DiffBadge icon status={element.status} className="p-0.5" /> {element.peer_label}
<DiffBadge size="icon" status={element.status} className="p-0.5" /> {element.peer_label}
{element.conflict && <BadgeConflict>Conflict</BadgeConflict>}
</div>

Expand Down
8 changes: 5 additions & 3 deletions frontend/app/src/screens/diff/node-diff/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,20 @@ export const diffBadges: { [key: string]: BadgeType } = {

export const DiffBadge = ({
status,
icon,
size = "default",
children,
...props
}: DiffBadgeProps & { status: string; icon?: boolean }) => {
}: DiffBadgeProps & { status: string; size?: "icon" | "default" }) => {
const DiffBadgeComp = diffBadges[status];

if (!DiffBadgeComp) {
return null;
}

return (
<DiffBadgeComp {...props}>{!icon && (children ?? capitalizeFirstLetter(status))}</DiffBadgeComp>
<DiffBadgeComp {...props}>
{size !== "icon" && (children ?? capitalizeFirstLetter(status))}
</DiffBadgeComp>
);
};

Expand Down
30 changes: 17 additions & 13 deletions frontend/app/src/screens/ipam/ipam-tree/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export const ROOT_TREE_ITEM: TreeItemProps["element"] = {
name: "",
parent: null,
children: [],
isBranch: true,
};

export const EMPTY_TREE: TreeProps["data"] = [ROOT_TREE_ITEM];
Expand Down Expand Up @@ -82,20 +81,25 @@ export const updateTreeData = (
};

export const addItemsToTree = (
currentTree: TreeProps["data"],
newItems: TreeProps["data"]
currentTreeItems: TreeProps["data"],
newTreeItems: TreeProps["data"]
): TreeProps["data"] => {
return newItems.reduce((acc, item) => {
const parentIndex = acc.findIndex(({ id }) => id === item.parent);
if (parentIndex !== -1) {
const parent = {
...acc[parentIndex],
children: [...new Set([...acc[parentIndex].children, item.id])],
};
return [...acc.slice(0, parentIndex), parent, ...acc.slice(parentIndex + 1), item];
const map = new Map<TreeProps["data"][0]["id"], TreeProps["data"][0]>();

// Build a map of items
[...currentTreeItems, ...newTreeItems].forEach((item) => {
map.set(item.id, item); // Initialize children as an empty array
});

newTreeItems.forEach((item) => {
const parentId = item.parent ?? TREE_ROOT_ID;
const parent = map.get(parentId);
if (parent) {
map.set(parent.id, { ...parent, children: [...new Set(parent.children.concat(item.id))] });
}
return [...acc, item];
}, currentTree);
});

return Array.from(map, ([, value]) => value);
};

export const getTreeItemAncestors = (
Expand Down
4 changes: 2 additions & 2 deletions frontend/app/src/screens/proposed-changes/create-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { ACCOUNT_OBJECT } from "@/config/constants";
export const ProposedChangeCreateForm = () => {
const { user } = useAuth();
const branches = useAtomValue(branchesState);
const defaultBranch = branches.filter((branch) => branch.is_default);
const defaultBranch = branches.find((branch) => branch.is_default);
const sourceBranches = branches.filter((branch) => !branch.is_default);
const navigate = useNavigate();

Expand Down Expand Up @@ -98,7 +98,7 @@ export const ProposedChangeCreateForm = () => {

<FormField
name="destination_branch"
defaultValue={defaultBranch[0].name}
defaultValue={defaultBranch?.name}
rules={{ required: "Required" }}
render={({ field }) => (
<div className="w-full relative mb-2 flex flex-col">
Expand Down
Loading

0 comments on commit a96adf2

Please sign in to comment.