Skip to content

Commit

Permalink
feat(graph): enable composite graph functionality
Browse files Browse the repository at this point in the history
This PR enables composite graph functionality:
- Experimental feature to enable Composite Graph
- In Composite Graph mode:
  - Nodes are shown by default.
  - Show/Hide All Projects function similarly to regular mode
  - Focus a Composite Node renders the inner nodes with up-to 3
additional containers: Green area contains external nodes that depend on
the inner nodes; Orange area contains external nodes that the inner
nodes depend depend on; Purple area contains external nodes with
circular dependencies with the inner nodes.
    - Focused node can be unfocus/reset.
    - Only one node can be focused at one given time.
    - Show All projects while having a focused node will unfocus the
node.
  - Expand a Composite Node renders the inner nodes of the composite
node in-place (i.e: still keep the context of the current graph).
Expanded node can be collapsed to go back.
  • Loading branch information
nartc committed Sep 5, 2024
1 parent 4068ecb commit fd402a5
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,43 @@ export const compositeGraphStateConfig: ProjectGraphStateNodeConfig = {
}),
],
on: {
selectAll: {
actions: [
assign((ctx, event) => {
if (event.type !== 'selectAll') return;
ctx.compositeGraph.enabled = true;
ctx.compositeGraph.context = null;
}),
send((ctx) => ({
type: 'enableCompositeGraph',
context: ctx.compositeGraph.context,
})),
],
},
deselectAll: {
actions: [
assign((ctx, event) => {
if (event.type !== 'deselectAll') return;
ctx.compositeGraph.enabled = true;
}),
send(
() => ({
type: 'notifyGraphHideAllProjects',
}),
{ to: (context) => context.graphActor }
),
],
},
selectAffected: {
actions: [
send(
() => ({
type: 'notifyGraphShowAffectedProjects',
}),
{ to: (context) => context.graphActor }
),
],
},
focusProject: {
actions: [
assign((ctx, event) => {
Expand Down Expand Up @@ -112,6 +149,7 @@ export const compositeGraphStateConfig: ProjectGraphStateNodeConfig = {
if (event.type !== 'enableCompositeGraph') return;
ctx.compositeGraph.enabled = true;
ctx.compositeGraph.context = event.context || undefined;
ctx.focusedProject = null;
}),
send(
(ctx, event) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ import { CheckboxPanel } from '../../ui-components/checkbox-panel';
export interface DisplayOptionsPanelProps {
groupByFolder: boolean;
groupByFolderChanged: (checked: boolean) => void;
disabled?: boolean;
disabledDescription?: string;
}

export const GroupByFolderPanel = ({
groupByFolder,
groupByFolderChanged,
disabled,
disabledDescription,
}: DisplayOptionsPanelProps) => {
return (
<CheckboxPanel
Expand All @@ -16,6 +20,8 @@ export const GroupByFolderPanel = ({
name={'groupByFolder'}
label={'Group by folder'}
description={'Visually arrange libraries by folders.'}
disabled={disabled}
disabledDescription={disabledDescription}
/>
);
};
11 changes: 2 additions & 9 deletions graph/client/src/app/feature-projects/project-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -283,13 +283,8 @@ function CompositeNodeListItem({
<div className="flex items-center">
<Link
to={routeConstructor(
{
pathname: `/projects`,
search: `?composite=true&compositeContext=${encodeURIComponent(
compositeNode.id
)}`,
},
false
{ pathname: `/projects`, search: `?composite=${compositeNode.id}` },
true
)}
className="mr-1 flex items-center rounded-md border-slate-300 bg-white p-1 font-medium text-slate-500 shadow-sm ring-1 ring-slate-200 transition hover:bg-slate-50 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-400 dark:ring-slate-600 hover:dark:bg-slate-700"
title="Focus on this node"
Expand Down Expand Up @@ -339,8 +334,6 @@ function CompositeNodeList({
}: {
compositeNodes: CompositeNode[];
}) {
const projectGraphService = getProjectGraphService();

if (compositeNodes.length === 0) {
return <p>No composite nodes</p>;
}
Expand Down
84 changes: 79 additions & 5 deletions graph/client/src/app/feature-projects/projects-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { useProjectGraphSelector } from './hooks/use-project-graph-selector';
import { TracingAlgorithmType } from './machines/interfaces';
import {
collapseEdgesSelector,
compositeContextSelector,
compositeGraphEnabledSelector,
focusedProjectNameSelector,
getTracingInfo,
groupByFolderSelector,
Expand Down Expand Up @@ -40,6 +42,8 @@ import {
} from 'react-router-dom';
import { useCurrentPath } from '../hooks/use-current-path';
import { ProjectDetailsModal } from '../ui-components/project-details-modal';
import { CompositeGraphPanel } from './panels/composite-graph-panel';
import { CompositeContextPanel } from '../ui-components/composite-context-panel';

export function ProjectsSidebar(): JSX.Element {
const environmentConfig = useEnvironmentConfig();
Expand All @@ -53,6 +57,10 @@ export function ProjectsSidebar(): JSX.Element {
);
const groupByFolder = useProjectGraphSelector(groupByFolderSelector);
const collapseEdges = useProjectGraphSelector(collapseEdgesSelector);
const compositeEnabled = useProjectGraphSelector(
compositeGraphEnabledSelector
);
const compositeContext = useProjectGraphSelector(compositeContextSelector);

const isTracing = projectGraphService.getSnapshot().matches('tracing');
const tracingInfo = useProjectGraphSelector(getTracingInfo);
Expand All @@ -75,17 +83,48 @@ export function ProjectsSidebar(): JSX.Element {
navigate(routeConstructor('/projects', true));
}

function resetCompositeContext() {
projectGraphService.send({ type: 'enableCompositeGraph', context: null });
navigate(
routeConstructor(
{ pathname: '/projects', search: '?composite=true' },
true
)
);
}

function showAllProjects() {
navigate(routeConstructor('/projects/all', true));
navigate(
routeConstructor('/projects/all', (searchParams) => {
if (searchParams.has('composite')) {
searchParams.set('composite', 'true');
}
return searchParams;
})
);
}

function hideAllProjects() {
projectGraphService.send({ type: 'deselectAll' });
navigate(routeConstructor('/projects', true));
navigate(
routeConstructor('/projects', (searchParams) => {
if (searchParams.has('composite')) {
searchParams.set('composite', 'true');
}
return searchParams;
})
);
}

function showAffectedProjects() {
navigate(routeConstructor('/projects/affected', true));
navigate(
routeConstructor('/projects/affected', (searchParams) => {
if (searchParams.has('composite')) {
searchParams.set('composite', 'true');
}
return searchParams;
})
);
}

function searchDepthFilterEnabledChange(checked: boolean) {
Expand Down Expand Up @@ -126,6 +165,17 @@ export function ProjectsSidebar(): JSX.Element {
});
}

function compositeEnabledChanged(checked: boolean) {
setSearchParams((currentSearchParams) => {
if (checked) {
currentSearchParams.set('composite', 'true');
} else {
currentSearchParams.delete('composite');
}
return currentSearchParams;
});
}

function incrementDepthFilter() {
const newSearchDepth = searchDepthInfo.searchDepth + 1;
setSearchParams((currentSearchParams) => {
Expand Down Expand Up @@ -224,7 +274,7 @@ export function ProjectsSidebar(): JSX.Element {
projectName: routeParams.endTrace,
});
}
}, [routeParams]);
}, [routeParams, compositeEnabled]);

useEffect(() => {
if (searchParams.has('groupByFolder') && groupByFolder === false) {
Expand All @@ -251,6 +301,17 @@ export function ProjectsSidebar(): JSX.Element {
});
}

if (searchParams.has('composite')) {
const compositeParam = searchParams.get('composite');
projectGraphService.send({
type: 'enableCompositeGraph',
context: compositeParam === 'true' ? null : compositeParam,
});
} else if (!searchParams.has('composite')) {
projectGraphService.send({ type: 'disableCompositeGraph' });
navigate(routeConstructor('/projects', true));
}

if (searchParams.has('searchDepth')) {
const parsedValue = parseInt(searchParams.get('searchDepth'), 10);

Expand Down Expand Up @@ -329,6 +390,13 @@ export function ProjectsSidebar(): JSX.Element {
<>
<ProjectDetailsModal />

{compositeEnabled && compositeContext ? (
<CompositeContextPanel
compositeContext={compositeContext}
reset={resetCompositeContext}
/>
) : null}

{focusedProject ? (
<FocusedPanel
focusedLabel={focusedProject}
Expand Down Expand Up @@ -367,6 +435,8 @@ export function ProjectsSidebar(): JSX.Element {
<GroupByFolderPanel
groupByFolder={groupByFolder}
groupByFolderChanged={groupByFolderChanged}
disabled={compositeEnabled}
disabledDescription="Group by folder is not available when composite graph is enabled"
></GroupByFolderPanel>

<SearchDepth
Expand All @@ -378,14 +448,18 @@ export function ProjectsSidebar(): JSX.Element {
></SearchDepth>

<ExperimentalFeature>
<div className="mx-4 mt-8 rounded-lg border-2 border-dashed border-purple-500 p-4 shadow-lg dark:border-purple-600 dark:bg-[#0B1221]">
<div className="mx-4 mt-8 flex flex-col gap-4 rounded-lg border-2 border-dashed border-purple-500 p-4 shadow-lg dark:border-purple-600 dark:bg-[#0B1221]">
<h3 className="cursor-text px-4 py-2 text-sm font-semibold uppercase tracking-wide text-slate-800 lg:text-xs dark:text-slate-200">
Experimental Features
</h3>
<CollapseEdgesPanel
collapseEdges={collapseEdges}
collapseEdgesChanged={collapseEdgesChanged}
></CollapseEdgesPanel>
<CompositeGraphPanel
compositeEnabled={compositeEnabled}
compositeEnabledChanged={compositeEnabledChanged}
></CompositeGraphPanel>
</div>
</ExperimentalFeature>
</div>
Expand Down
27 changes: 24 additions & 3 deletions graph/client/src/app/ui-components/checkbox-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,34 @@
import { memo } from 'react';
import classNames from 'classnames';

export interface CheckboxPanelProps {
checked: boolean;
checkChanged: (checked: boolean) => void;
name: string;
label: string;
description: string;
disabled?: boolean;
disabledDescription?: string;
}

export const CheckboxPanel = memo(
({ checked, checkChanged, label, description, name }: CheckboxPanelProps) => {
({
checked,
checkChanged,
label,
description,
name,
disabled,
disabledDescription,
}: CheckboxPanelProps) => {
return (
<div className="mt-8 px-4">
<div
className={classNames(
'mt-8 px-4',
disabled ? 'cursor-not-allowed opacity-50' : ''
)}
title={disabled ? disabledDescription : description}
>
<div className="flex items-start">
<div className="flex h-5 items-center">
<input
Expand All @@ -22,12 +39,16 @@ export const CheckboxPanel = memo(
className="h-4 w-4 accent-blue-500 dark:accent-sky-500"
onChange={(event) => checkChanged(event.target.checked)}
checked={checked}
disabled={disabled}
/>
</div>
<div className="ml-3 text-sm">
<label
htmlFor={name}
className="cursor-pointer font-medium text-slate-600 dark:text-slate-400"
className={classNames(
' font-medium text-slate-600 dark:text-slate-400',
disabled ? 'cursor-not-allowed' : 'cursor-pointer'
)}
>
{label}
</label>
Expand Down
21 changes: 10 additions & 11 deletions graph/client/src/app/ui-tooltips/graph-tooltip-display.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,16 @@ export function TooltipDisplay() {
});
break;
case 'focus-node': {
const to =
action.tooltipNodeType === 'compositeNode'
? routeConstructor(
{
pathname: `/projects`,
search: `?composite=true&compositeContext=${action.id}`,
},
false
)
: routeConstructor(`/projects/${action.id}`, true);
navigate(to);
if (action.tooltipNodeType === 'compositeNode') {
navigate(
routeConstructor(
{ pathname: `/projects`, search: `?composite=${action.id}` },
true
)
);
} else {
navigate(routeConstructor(`/projects/${action.id}`, true));
}
break;
}
case 'collapse-node':
Expand Down

0 comments on commit fd402a5

Please sign in to comment.