Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(graph): enable composite graph functionality #27789

Merged
merged 8 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ export interface CompositeGraphPanelProps {
export const CompositeGraphPanel = memo(
({ compositeEnabled, compositeEnabledChanged }: CompositeGraphPanelProps) => {
return (
<div className="px-4">
<div className="mt-4 px-4">
<div className="flex items-start">
<div className="flex h-5 items-center">
<input
id="composite"
name="composite"
value="composite"
type="checkbox"
className="h-4 w-4 accent-purple-500"
className="h-4 w-4 accent-blue-500 dark:accent-sky-500"
onChange={(event) =>
compositeEnabledChanged(event.target.checked)
}
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}
/>
);
};
20 changes: 9 additions & 11 deletions graph/client/src/app/feature-projects/project-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { getProjectGraphService } from '../machines/get-services';
import { Link, useNavigate, useNavigation } from 'react-router-dom';
import { useRouteConstructor } from '@nx/graph/shared';
import { CompositeNode } from '../interfaces';
import { useMemo } from 'react';

interface SidebarProject {
projectGraphNode: ProjectGraphProjectNode;
Expand Down Expand Up @@ -249,6 +250,10 @@ function CompositeNodeListItem({
const routeConstructor = useRouteConstructor();
const navigate = useNavigate();

const label = compositeNode.parent
? `${compositeNode.parent}/${compositeNode.label}`
: compositeNode.label;

function toggleProject() {
if (compositeNode.state !== 'hidden') {
projectGraphService.send({
Expand Down Expand Up @@ -283,13 +288,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 All @@ -313,11 +313,11 @@ function CompositeNodeListItem({
<label
className="ml-2 block w-full cursor-pointer truncate rounded-md p-2 font-mono font-normal transition hover:bg-slate-50 hover:dark:bg-slate-700"
data-project={compositeNode.id}
title={compositeNode.label}
title={label}
data-active={compositeNode.state !== 'hidden'}
onClick={toggleProject}
>
{compositeNode.label}
{label}
</label>
</div>

Expand All @@ -339,8 +339,6 @@ function CompositeNodeList({
}: {
compositeNodes: CompositeNode[];
}) {
const projectGraphService = getProjectGraphService();

if (compositeNodes.length === 0) {
return <p>No composite nodes</p>;
}
Expand Down
103 changes: 98 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,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);
Expand All @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -224,7 +292,7 @@ export function ProjectsSidebar(): JSX.Element {
projectName: routeParams.endTrace,
});
}
}, [routeParams]);
}, [routeParams, compositeEnabled]);

useEffect(() => {
if (searchParams.has('groupByFolder') && groupByFolder === false) {
Expand All @@ -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);

Expand Down Expand Up @@ -329,6 +408,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 +453,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 @@ -377,8 +465,13 @@ export function ProjectsSidebar(): JSX.Element {
decrementDepthFilter={decrementDepthFilter}
></SearchDepth>

<CompositeGraphPanel
compositeEnabled={compositeEnabled}
compositeEnabledChanged={compositeEnabledChanged}
></CompositeGraphPanel>

<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>
Expand Down
1 change: 1 addition & 0 deletions graph/client/src/app/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ export interface CompositeNode {
id: string;
label: string;
state: 'expanded' | 'collapsed' | 'hidden';
parent?: string;
}
Loading