Skip to content

Commit

Permalink
feat: provide groups live dependencies visualization
Browse files Browse the repository at this point in the history
  • Loading branch information
antoine-coulon committed Jul 9, 2024
1 parent 1237352 commit 3cf902e
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 58 deletions.
188 changes: 143 additions & 45 deletions apps/web/src/sidebar/summary/group/GroupNode.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import { ActionIcon, Box, Flex, Text } from "@mantine/core";
import { ActionIcon, Box, Flex, Menu, Text } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";

import {
IconCornerDownRight,
IconCornerLeftUp,
IconDots,
IconFocusCentered,
IconHierarchy3,
IconPhotoSensor3,
IconRefreshAlert,
IconTopologyComplex,
} from "@tabler/icons-react";
import React from "react";
import { DiGraph } from "digraph-js";
import type { SkottNode } from "skott/graph/node";
import { DiGraph, VertexBody, VertexDefinition } from "digraph-js";
import { notify } from "@/store/store";

function AdjacentNode({ id, adjacentId }: { id: string; adjacentId: string }) {
function AdjacentNode({
id,
adjacentId,
direction,
}: {
id: string;
adjacentId: string;
direction: "upstream" | "downstream";
}) {
return (
<Flex>
{adjacentId === id ? (
Expand All @@ -31,7 +39,11 @@ function AdjacentNode({ id, adjacentId }: { id: string; adjacentId: string }) {
</>
) : (
<>
<IconCornerDownRight color="teal" />
{direction === "downstream" ? (
<IconCornerDownRight color="teal" />
) : (
<IconCornerLeftUp color="teal" />
)}

<Text
style={{
Expand All @@ -47,21 +59,53 @@ function AdjacentNode({ id, adjacentId }: { id: string; adjacentId: string }) {
);
}

function DeepParents(props: { id: string; compute: () => Generator<string> }) {
const list = Array.from(props.compute());

if (list.length === 0) {
return (
<Text ml="lg" color="orange" size="xs">
No parents found
</Text>
);
}

return (
<Box pl="sm" style={{ marginTop: 0, paddingTop: 0 }}>
{list.map((adjacentId) => {
return (
<AdjacentNode
key={"deep-parents=" + adjacentId}
id={props.id}
adjacentId={adjacentId}
direction="upstream"
/>
);
})}
</Box>
);
}

export function GroupNode({
id,
adjacentTo,
graph,
}: {
id: string;
adjacentTo: string[];
graph: Record<string, SkottNode<unknown>>;
graph: DiGraph<VertexDefinition<VertexBody>>;
}) {
const graphInstance = React.useMemo(() => DiGraph.fromRaw(graph), [graph]);
const [shallowOpened, shallowHandlers] = useDisclosure();
const [deepOpened, deepHandlers] = useDisclosure();
const [directChildrenOpened, directChildrenHandlers] = useDisclosure();
const [deepChildrenOpened, deepChildrenHandlers] = useDisclosure();
const [deepParentsOpened, deepParentsHandlers] = useDisclosure();

function computeDeepChildrenList(id: string) {
return graph.getDeepChildren(id);
}

function computeDeepAdjacencyList(id: string) {
return graphInstance.getDeepChildren(id);
function computeDeepParentList(id: string) {
console.log("done");
return graph.getDeepParents(id);
}

return (
Expand All @@ -71,66 +115,120 @@ export function GroupNode({
{id}
</Text>

<Box>
<Flex>
<Menu
transitionProps={{ transition: "pop-top-right" }}
position="right"
withinPortal
>
<Menu.Target>
<ActionIcon size="lg">
<IconDots size="1rem" />
</ActionIcon>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item
onClick={() => {
notify({
action: "focus_on_node",
payload: { nodeId: id },
});
}}
icon={
<IconFocusCentered size="1.3rem" color="violet" stroke={1.5} />
}
>
Focus
</Menu.Item>

{adjacentTo.length > 0 ? (
<>
<ActionIcon
color={shallowOpened ? "teal" : undefined}
size={25}
<Menu.Item
onClick={() => {
// todo: highlight the adjacent nodes in the network
shallowHandlers.toggle();
deepHandlers.close();
directChildrenHandlers.toggle();
deepChildrenHandlers.close();
deepParentsHandlers.close();
}}
icon={
<IconTopologyComplex
color={directChildrenOpened ? "teal" : undefined}
/>
}
>
<IconTopologyComplex />
</ActionIcon>
<ActionIcon
size={25}
color={deepOpened ? "teal" : undefined}
Toggle direct children dependencies
</Menu.Item>

<Menu.Item
icon={
<IconHierarchy3
color={deepChildrenOpened ? "teal" : undefined}
/>
}
onClick={() => {
// todo: highlight the adjacent nodes in the network
deepHandlers.toggle();
shallowHandlers.close();
deepChildrenHandlers.toggle();
directChildrenHandlers.close();
deepParentsHandlers.close();
}}
>
<IconHierarchy3 />
</ActionIcon>
Toggle deep children dependencies
</Menu.Item>
</>
) : null}

<ActionIcon
size={25}
color="magenta"
<Menu.Item
icon={
<IconHierarchy3
color={deepParentsOpened ? "teal" : undefined}
style={{
rotate: "180deg",
}}
/>
}
onClick={() => {
notify({
action: "focus_on_node",
payload: { nodeId: id },
});
// todo: highlight the adjacent nodes in the network
deepParentsHandlers.toggle();
directChildrenHandlers.close();
deepChildrenHandlers.close();
}}
>
<IconPhotoSensor3 />
</ActionIcon>
</Flex>
</Box>
Toggle deep parent dependencies
</Menu.Item>
</Menu.Dropdown>
</Menu>
</Flex>

{shallowOpened ? (
{directChildrenOpened ? (
<Box pl="sm" style={{ marginTop: 0, paddingTop: 0 }}>
{adjacentTo.map((adjacentId) => (
<AdjacentNode id={id} adjacentId={adjacentId} />
<AdjacentNode
key={"direct-children=" + adjacentId}
id={id}
adjacentId={adjacentId}
direction="downstream"
/>
))}
</Box>
) : null}

{deepOpened ? (
{deepChildrenOpened ? (
<Box pl="sm" style={{ marginTop: 0, paddingTop: 0 }}>
{Array.from(computeDeepAdjacencyList(id), (adjacentId) => {
return <AdjacentNode id={id} adjacentId={adjacentId} />;
{Array.from(computeDeepChildrenList(id), (adjacentId) => {
return (
<AdjacentNode
key={"deep-children=" + adjacentId}
id={id}
adjacentId={adjacentId}
direction="downstream"
/>
);
})}
</Box>
) : null}

{deepParentsOpened ? (
<DeepParents id={id} compute={() => computeDeepParentList(id)} />
) : null}
</Box>
);
}
22 changes: 18 additions & 4 deletions apps/web/src/sidebar/summary/group/Groups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ import {
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { IconHelpCircle, IconInfoCircle } from "@tabler/icons-react";
import { DiGraph } from "digraph-js";

/**
* Not used yet, but will be once we enable the visualization for custom
* grouped graphs
*/
export function GroupedGraphDocumentation() {
const [opened, { open, close }] = useDisclosure(false);

Expand Down Expand Up @@ -60,7 +65,9 @@ export function Groups() {
return null;
}

const graph = maybeGraph.value;
const rawGraph = maybeGraph.value;
const digraph = DiGraph.fromRaw(rawGraph);

return (
<ScrollArea.Autosize mah="90vh" mx="auto">
<Navbar.Section>
Expand All @@ -73,15 +80,22 @@ export function Groups() {
gradient={{ from: "indigo", to: "blue" }}
size="lg"
>
{Object.keys(graph).length}
{Object.keys(rawGraph).length}
</Badge>
</Flex>
</Paper>
</Box>

<Box p="md">
{Object.values(graph).map(({ id, adjacentTo }) => {
return <GroupNode id={id} adjacentTo={adjacentTo} graph={graph} />;
{Object.values(rawGraph).map(({ id, adjacentTo }) => {
return (
<GroupNode
key={id}
id={id}
adjacentTo={adjacentTo}
graph={digraph}
/>
);
})}
</Box>
</Navbar.Section>
Expand Down
8 changes: 4 additions & 4 deletions apps/web/src/store/react-bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ export const useStoreSelect = <
K extends keyof AppState[T],
>(
storeSegment: T,
pluckedProperty: K,
pluckedProperty: K
) => {
const [state, setState] = React.useState<Option.Option<AppState[T][K]>>(
Option.none,
Option.none
);
const store = useAppStore();

Expand All @@ -32,13 +32,13 @@ export const useStoreSelect = <
return () => {
subscription.unsubscribe();
};
});
}, []);

return state;
};

export function isSelectorAvailable<K>(
selector: Option.Option<K>,
selector: Option.Option<K>
): selector is Option.Some<K> {
return selector._tag === "Some";
}
Expand Down
24 changes: 19 additions & 5 deletions apps/web/styles/index.css
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
html {
height: 100vh;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif,
Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
font-family:
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
Segoe UI,
Roboto,
Helvetica Neue,
Arial,
Noto Sans,
sans-serif,
Apple Color Emoji,
Segoe UI Emoji,
Segoe UI Symbol,
Noto Color Emoji;
font-weight: 500;
}

Expand Down Expand Up @@ -44,7 +56,9 @@ kbd {
background: #ffd349;
border: 0;
border-radius: 3px;
box-shadow: inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px #51577d,
box-shadow:
inset 0 -2px 0 0 #282d55,
inset 0 0 1px 1px #51577d,
0 2px 2px 0 rgba(3, 4, 9, 0.3);
color: black;
display: flex;
Expand Down Expand Up @@ -86,7 +100,7 @@ ninja-keys {
--ninja-actions-height: 300px;
--ninja-group-text-color: rgb(3, 118, 213);
--ninja-placeholder-color: #8e8e8e;
--ninja-z-index: 1;
--ninja-z-index: 10000;
}

/** Mantine overrides **/
Expand Down

0 comments on commit 3cf902e

Please sign in to comment.