Skip to content

Commit

Permalink
feat(graph): show partial project graph & errors in graph app (#22838)
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxKless committed Apr 30, 2024
1 parent 0ceea2f commit c8d44b0
Show file tree
Hide file tree
Showing 27 changed files with 615 additions and 76 deletions.
2 changes: 1 addition & 1 deletion graph/client/src/app/feature-projects/project-list.tsx
Expand Up @@ -219,7 +219,7 @@ function SubProjectList({
</span>
</div>
) : null}
<ul className="mt-2 -ml-3">
<ul className="-ml-3 mt-2">
{sortedProjects.map((project) => {
return (
<ProjectListItem
Expand Down
17 changes: 15 additions & 2 deletions graph/client/src/app/routes.tsx
@@ -1,12 +1,16 @@
import { redirect, RouteObject } from 'react-router-dom';
import { redirect, RouteObject, json } from 'react-router-dom';
import { ProjectsSidebar } from './feature-projects/projects-sidebar';
import { TasksSidebar } from './feature-tasks/tasks-sidebar';
import { Shell } from './shell';
/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
import { ProjectGraphClientResponse } from 'nx/src/command-line/graph/graph';
import type {
GraphError,
ProjectGraphClientResponse,
} from 'nx/src/command-line/graph/graph';
// nx-ignore-next-line
import type { ProjectGraphProjectNode } from 'nx/src/config/project-graph';
/* eslint-enable @nx/enforce-module-boundaries */
import {
getEnvironmentConfig,
getProjectGraphDataService,
Expand Down Expand Up @@ -78,17 +82,26 @@ const projectDetailsLoader = async (
hash: string;
project: ProjectGraphProjectNode;
sourceMap: Record<string, string[]>;
errors?: GraphError[];
}> => {
const workspaceData = await workspaceDataLoader(selectedWorkspaceId);
const sourceMaps = await sourceMapsLoader(selectedWorkspaceId);

const project = workspaceData.projects.find(
(project) => project.name === projectName
);
if (!project) {
throw json({
id: 'project-not-found',
projectName,
errors: workspaceData.errors,
});
}
return {
hash: workspaceData.hash,
project,
sourceMap: sourceMaps[project.data.root],
errors: workspaceData.errors,
};
};

Expand Down
66 changes: 53 additions & 13 deletions graph/client/src/app/shell.tsx
@@ -1,30 +1,48 @@
/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
import {
GraphError,
ProjectGraphClientResponse,
} from 'nx/src/command-line/graph/graph';
/* eslint-enable @nx/enforce-module-boundaries */

import {
ArrowDownTrayIcon,
ArrowLeftCircleIcon,
InformationCircleIcon,
} from '@heroicons/react/24/outline';
import {
ErrorToast,
fetchProjectGraph,
getProjectGraphDataService,
useEnvironmentConfig,
useIntervalWhen,
} from '@nx/graph/shared';
import { Dropdown, Spinner } from '@nx/graph/ui-components';
import { getSystemTheme, Theme, ThemePanel } from '@nx/graph/ui-theme';
import { Tooltip } from '@nx/graph/ui-tooltips';
import classNames from 'classnames';
import { DebuggerPanel } from './ui-components/debugger-panel';
import { getGraphService } from './machines/graph.service';
import { useLayoutEffect, useState } from 'react';
import {
Outlet,
useNavigate,
useNavigation,
useParams,
useRouteLoaderData,
} from 'react-router-dom';
import { getSystemTheme, Theme, ThemePanel } from '@nx/graph/ui-theme';
import { Dropdown, Spinner } from '@nx/graph/ui-components';
import { useCurrentPath } from './hooks/use-current-path';
import { ExperimentalFeature } from './ui-components/experimental-feature';
import { useSyncExternalStore } from 'use-sync-external-store/shim';
import { RankdirPanel } from './feature-projects/panels/rankdir-panel';
import { useCurrentPath } from './hooks/use-current-path';
import { getProjectGraphService } from './machines/get-services';
import { useSyncExternalStore } from 'use-sync-external-store/shim';
import { Tooltip } from '@nx/graph/ui-tooltips';
import { getGraphService } from './machines/graph.service';
import { DebuggerPanel } from './ui-components/debugger-panel';
import { ExperimentalFeature } from './ui-components/experimental-feature';
import { TooltipDisplay } from './ui-tooltips/graph-tooltip-display';
import { useEnvironmentConfig } from '@nx/graph/shared';

export function Shell(): JSX.Element {
const projectGraphService = getProjectGraphService();
const projectGraphDataService = getProjectGraphDataService();

const graphService = getGraphService();

const lastPerfReport = useSyncExternalStore(
Expand All @@ -43,9 +61,30 @@ export function Shell(): JSX.Element {
const navigate = useNavigate();
const { state: navigationState } = useNavigation();
const currentPath = useCurrentPath();
const { selectedWorkspaceId } = useParams();
const params = useParams();
const currentRoute = currentPath.currentPath;

const [errors, setErrors] = useState<GraphError[] | undefined>(undefined);
const { errors: routerErrors } = useRouteLoaderData('selectedWorkspace') as {
errors: GraphError[];
};
useLayoutEffect(() => {
setErrors(routerErrors);
}, [routerErrors]);
useIntervalWhen(
() => {
fetchProjectGraph(
projectGraphDataService,
params,
environmentConfig.appConfig
).then((response: ProjectGraphClientResponse) => {
setErrors(response.errors);
});
},
1000,
environmentConfig.watch
);

const topLevelRoute = currentRoute.startsWith('/tasks')
? '/tasks'
: '/projects';
Expand Down Expand Up @@ -84,7 +123,7 @@ export function Shell(): JSX.Element {
<div
className={`${
environmentConfig.environment === 'nx-console'
? 'absolute top-5 left-5 z-50 bg-white'
? 'absolute left-5 top-5 z-50 bg-white'
: 'relative flex h-full overflow-y-scroll'
} w-72 flex-col pb-10 shadow-lg ring-1 ring-slate-900/10 ring-opacity-10 transition-all dark:ring-slate-300/10`}
id="sidebar"
Expand Down Expand Up @@ -165,7 +204,7 @@ export function Shell(): JSX.Element {
{environment.appConfig.showDebugger ? (
<DebuggerPanel
projects={environment.appConfig.workspaces}
selectedProject={selectedWorkspaceId}
selectedProject={params.selectedWorkspaceId}
lastPerfReport={lastPerfReport}
selectedProjectChange={projectChange}
></DebuggerPanel>
Expand Down Expand Up @@ -212,11 +251,12 @@ export function Shell(): JSX.Element {
data-cy="downloadImageButton"
onClick={downloadImage}
>
<ArrowDownTrayIcon className="absolute top-1/2 left-1/2 -mt-3 -ml-3 h-6 w-6" />
<ArrowDownTrayIcon className="absolute left-1/2 top-1/2 -ml-3 -mt-3 h-6 w-6" />
</button>
</Tooltip>
</div>
</div>
<ErrorToast errors={errors} />
</div>
);
}
99 changes: 86 additions & 13 deletions graph/client/src/app/ui-components/error-boundary.tsx
@@ -1,27 +1,100 @@
import { useEnvironmentConfig } from '@nx/graph/shared';
import { ProjectDetailsHeader } from 'graph/project-details/src/lib/project-details-header';
import { useRouteError } from 'react-router-dom';
import { ProjectDetailsHeader } from '@nx/graph/project-details';
import {
fetchProjectGraph,
getProjectGraphDataService,
useEnvironmentConfig,
useIntervalWhen,
} from '@nx/graph/shared';
import { ErrorRenderer } from '@nx/graph/ui-components';
import {
isRouteErrorResponse,
useParams,
useRouteError,
} from 'react-router-dom';

export function ErrorBoundary() {
let error = useRouteError();
console.error(error);
const environment = useEnvironmentConfig()?.environment;

let message = 'Disconnected from graph server. ';
if (environment === 'nx-console') {
message += 'Please refresh the page.';
const { environment, appConfig, watch } = useEnvironmentConfig();
const projectGraphDataService = getProjectGraphDataService();
const params = useParams();

const hasErrorData =
isRouteErrorResponse(error) && error.data.errors?.length > 0;

useIntervalWhen(
async () => {
fetchProjectGraph(projectGraphDataService, params, appConfig).then(
(data) => {
if (
isRouteErrorResponse(error) &&
error.data.id === 'project-not-found' &&
data.projects.find((p) => p.name === error.data.projectName)
) {
window.location.reload();
}
return;
}
);
},
1000,
watch
);

let message: string | JSX.Element;
let stack: string;
if (isRouteErrorResponse(error) && error.data.id === 'project-not-found') {
message = (
<p>
Project <code>{error.data.projectName}</code> not found.
</p>
);
} else {
message += 'Please rerun your command and refresh the page.';
message = 'Disconnected from graph server. ';
if (environment === 'nx-console') {
message += 'Please refresh the page.';
} else {
message += 'Please rerun your command and refresh the page.';
}
stack = error.toString();
}

return (
<div className="flex h-screen w-full flex-col items-center">
<ProjectDetailsHeader />
<h1 className="mb-4 text-4xl dark:text-slate-100">Error</h1>
<div>
<p className="mb-4 text-lg dark:text-slate-200">{message}</p>
<p className="text-sm">Error message: {error?.toString()}</p>
{environment !== 'nx-console' && <ProjectDetailsHeader />}
<div className="mx-auto mb-8 w-full max-w-6xl flex-grow px-8">
<h1 className="mb-4 text-4xl dark:text-slate-100">Error</h1>
<div>
<ErrorWithStack message={message} stack={stack} />
</div>
{hasErrorData && (
<div>
<p className="text-md mb-4 dark:text-slate-200">
Nx encountered the following issues while processing the project
graph:{' '}
</p>
<div>
<ErrorRenderer errors={error.data.errors} />
</div>
</div>
)}
</div>
</div>
);
}

function ErrorWithStack({
message,
stack,
}: {
message: string | JSX.Element;
stack?: string;
}) {
return (
<div>
<p className="mb-4 text-lg dark:text-slate-100">{message}</p>
{stack && <p className="text-sm">Error message: {stack}</p>}
</div>
);
}
14 changes: 7 additions & 7 deletions graph/client/src/app/ui-components/project-details-modal.tsx
@@ -1,11 +1,11 @@
/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
import { ProjectGraphClientResponse } from 'nx/src/command-line/graph/graph';
// nx-ignore-next-line
import { ProjectDetailsWrapper } from '@nx/graph/project-details';
/* eslint-enable @nx/enforce-module-boundaries */
import { useFloating } from '@floating-ui/react';
import { XMarkIcon } from '@heroicons/react/24/outline';
import { ProjectDetailsWrapper } from '@nx/graph/project-details';
/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
import { ProjectGraphClientResponse } from 'nx/src/command-line/graph/graph';
import { useEffect, useState } from 'react';
import { useRouteLoaderData, useSearchParams } from 'react-router-dom';

Expand Down Expand Up @@ -50,15 +50,15 @@ export function ProjectDetailsModal() {
return (
isOpen && (
<div
className="top-24 z-20 right-4 opacity-100 bg-white dark:bg-slate-800 fixed h-max w-1/3"
className="fixed right-4 top-24 z-20 h-max w-1/3 bg-white opacity-100 dark:bg-slate-800"
style={{
height: 'calc(100vh - 6rem - 2rem)',
}}
ref={refs.setFloating}
>
<div className="rounded-md h-full border border-slate-500">
<div className="h-full rounded-md border border-slate-500">
<ProjectDetailsWrapper project={project} sourceMap={sourceMap} />
<div className="top-2 right-2 absolute" onClick={onClose}>
<div className="absolute right-2 top-2" onClick={onClose}>
<XMarkIcon className="h-4 w-4" />
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions graph/project-details/src/index.ts
@@ -1,2 +1,3 @@
export * from './lib/project-details-wrapper';
export * from './lib/project-details-page';
export * from './lib/project-details-header';
10 changes: 8 additions & 2 deletions graph/project-details/src/lib/project-details-page.tsx
@@ -1,6 +1,10 @@
/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
import type { ProjectGraphProjectNode } from '@nx/devkit';
import { ProjectGraphProjectNode } from '@nx/devkit';
// nx-ignore-next-line
import { GraphError } from 'nx/src/command-line/graph/graph';
/* eslint-enable @nx/enforce-module-boundaries */

import {
ScrollRestoration,
useParams,
Expand All @@ -16,12 +20,13 @@ import {
import { ProjectDetailsHeader } from './project-details-header';

export function ProjectDetailsPage() {
const { project, sourceMap, hash } = useRouteLoaderData(
const { project, sourceMap, hash, errors } = useRouteLoaderData(
'selectedProjectDetails'
) as {
hash: string;
project: ProjectGraphProjectNode;
sourceMap: Record<string, string[]>;
errors?: GraphError[];
};

const { environment, watch, appConfig } = useEnvironmentConfig();
Expand Down Expand Up @@ -56,6 +61,7 @@ export function ProjectDetailsPage() {
<ProjectDetailsWrapper
project={project}
sourceMap={sourceMap}
errors={errors}
></ProjectDetailsWrapper>
</div>
</div>
Expand Down

0 comments on commit c8d44b0

Please sign in to comment.