diff --git a/graph/client/src/app/feature-projects/machines/composite-graph.state.ts b/graph/client/src/app/feature-projects/machines/composite-graph.state.ts index 3f7dcbc489..e73bfab9a6 100644 --- a/graph/client/src/app/feature-projects/machines/composite-graph.state.ts +++ b/graph/client/src/app/feature-projects/machines/composite-graph.state.ts @@ -25,15 +25,65 @@ export const compositeGraphStateConfig: ProjectGraphStateNodeConfig = { ), ], exit: [ - send(() => ({ type: 'notifyGraphDisableCompositeGraph' }), { - to: (ctx) => ctx.graphActor, - }), assign((ctx) => { ctx.compositeGraph.enabled = false; ctx.compositeGraph.context = undefined; }), + send( + (ctx) => ({ + type: 'notifyGraphUpdateGraph', + projects: ctx.projects, + dependencies: ctx.dependencies, + fileMap: ctx.fileMap, + affectedProjects: ctx.affectedProjects, + workspaceLayout: ctx.workspaceLayout, + groupByFolder: ctx.groupByFolder, + selectedProjects: ctx.selectedProjects, + composite: ctx.compositeGraph, + }), + { + to: (ctx) => ctx.graphActor, + } + ), ], 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) => { @@ -112,6 +162,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) => ({ diff --git a/graph/client/src/app/feature-projects/panels/composite-graph-panel.tsx b/graph/client/src/app/feature-projects/panels/composite-graph-panel.tsx index 0b1a147de8..3969b660bb 100644 --- a/graph/client/src/app/feature-projects/panels/composite-graph-panel.tsx +++ b/graph/client/src/app/feature-projects/panels/composite-graph-panel.tsx @@ -8,7 +8,7 @@ export interface CompositeGraphPanelProps { export const CompositeGraphPanel = memo( ({ compositeEnabled, compositeEnabledChanged }: CompositeGraphPanelProps) => { return ( -
No composite nodes
; } diff --git a/graph/client/src/app/feature-projects/projects-sidebar.tsx b/graph/client/src/app/feature-projects/projects-sidebar.tsx index 676f318bdb..95380ecefd 100644 --- a/graph/client/src/app/feature-projects/projects-sidebar.tsx +++ b/graph/client/src/app/feature-projects/projects-sidebar.tsx @@ -7,6 +7,8 @@ import { useProjectGraphSelector } from './hooks/use-project-graph-selector'; import { TracingAlgorithmType } from './machines/interfaces'; import { collapseEdgesSelector, + compositeContextSelector, + compositeGraphEnabledSelector, focusedProjectNameSelector, getTracingInfo, groupByFolderSelector, @@ -40,9 +42,13 @@ 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'; +import { getGraphService } from '../machines/graph.service'; export function ProjectsSidebar(): JSX.Element { const environmentConfig = useEnvironmentConfig(); + const graphService = getGraphService(); const projectGraphService = getProjectGraphService(); const focusedProject = useProjectGraphSelector(focusedProjectNameSelector); const searchDepthInfo = useProjectGraphSelector(searchDepthSelector); @@ -53,6 +59,11 @@ 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); @@ -75,17 +86,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) { @@ -126,6 +168,19 @@ export function ProjectsSidebar(): JSX.Element { }); } + function compositeEnabledChanged(checked: boolean) { + navigate( + routeConstructor('/projects', (searchParams) => { + if (checked) { + searchParams.set('composite', 'true'); + } else { + searchParams.delete('composite'); + } + return searchParams; + }) + ); + } + function incrementDepthFilter() { const newSearchDepth = searchDepthInfo.searchDepth + 1; setSearchParams((currentSearchParams) => { @@ -182,6 +237,19 @@ export function ProjectsSidebar(): JSX.Element { }); } + useEffect(() => { + return graphService.listen((event) => { + if (event.type === 'CompositeNodeDblClick') { + projectGraphService.send({ + type: event.data.expanded + ? 'collapseCompositeNode' + : 'expandCompositeNode', + id: event.id, + }); + } + }); + }, []); + useEffect(() => { projectGraphService.send({ type: 'setProjects', @@ -224,7 +292,7 @@ export function ProjectsSidebar(): JSX.Element { projectName: routeParams.endTrace, }); } - }, [routeParams]); + }, [routeParams, compositeEnabled]); useEffect(() => { if (searchParams.has('groupByFolder') && groupByFolder === false) { @@ -251,6 +319,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); @@ -329,6 +408,13 @@ export function ProjectsSidebar(): JSX.Element { <>