Skip to content

Commit

Permalink
feat(graph): show partial project graph & errors in graph app
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxKless committed Apr 16, 2024
1 parent 2ee9650 commit 59b139e
Show file tree
Hide file tree
Showing 19 changed files with 603 additions and 68 deletions.
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
62 changes: 51 additions & 11 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 @@ -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 @@ -217,6 +256,7 @@ export function Shell(): JSX.Element {
</Tooltip>
</div>
</div>
<ErrorToast errors={errors} />
</div>
);
}
97 changes: 85 additions & 12 deletions graph/client/src/app/ui-components/error-boundary.tsx
@@ -1,27 +1,100 @@
import { useEnvironmentConfig } from '@nx/graph/shared';
import {
fetchProjectGraph,
getProjectGraphDataService,
useEnvironmentConfig,
useIntervalWhen,
} from '@nx/graph/shared';
import { ErrorRenderer } from '@nx/graph/ui-components';
import { ProjectDetailsHeader } from 'graph/project-details/src/lib/project-details-header';
import { useRouteError } from 'react-router-dom';
import {
isRouteErrorResponse,
useParams,
useRouteError,
} from 'react-router-dom';

export function ErrorBoundary() {
let error = useRouteError()?.toString();
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 flex-col items-center h-screen w-full">
<ProjectDetailsHeader />
<h1 className="text-4xl mb-4 dark:text-slate-100">Error</h1>
<div>
<p className="text-lg mb-4 dark:text-slate-200">{message}</p>
<p className="text-sm">Error message: {error}</p>
<div className="flex-grow mx-auto w-full max-w-6xl px-8 mb-8">
<h1 className="text-4xl mb-4 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="text-lg mb-4 dark:text-slate-200">{message}</p>
{stack && <p className="text-sm">Error message: {stack}</p>}
</div>
);
}
8 changes: 4 additions & 4 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
8 changes: 7 additions & 1 deletion graph/project-details/src/lib/project-details-page.tsx
@@ -1,6 +1,10 @@
/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
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
28 changes: 18 additions & 10 deletions graph/project-details/src/lib/project-details-wrapper.tsx
Expand Up @@ -4,8 +4,12 @@ import { useNavigate, useSearchParams } from 'react-router-dom';

/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
import { ProjectGraphProjectNode } from '@nx/devkit';
import type { ProjectGraphProjectNode } from '@nx/devkit';
// nx-ignore-next-line
import { GraphError } from 'nx/src/command-line/graph/graph';
/* eslint-enable @nx/enforce-module-boundaries */
import {
ErrorToast,
getExternalApiService,
useEnvironmentConfig,
useRouteConstructor,
Expand All @@ -19,6 +23,7 @@ import { useCallback, useLayoutEffect, useRef } from 'react';
export interface ProjectDetailsProps {
project: ProjectGraphProjectNode;
sourceMap: Record<string, string[]>;
errors?: GraphError[];
}

export function ProjectDetailsWrapper(props: ProjectDetailsProps) {
Expand Down Expand Up @@ -144,15 +149,18 @@ export function ProjectDetailsWrapper(props: ProjectDetailsProps) {
}, [searchParams, props.project.data.targets, projectDetailsRef]);

return (
<ProjectDetails
ref={projectDetailsRef}
{...props}
onTargetCollapse={handleTargetCollapse}
onTargetExpand={handleTargetExpand}
onViewInProjectGraph={handleViewInProjectGraph}
onViewInTaskGraph={handleViewInTaskGraph}
onRunTarget={environment === 'nx-console' ? handleRunTarget : undefined}
/>
<>
<ProjectDetails
ref={projectDetailsRef}
{...props}
onTargetCollapse={handleTargetCollapse}
onTargetExpand={handleTargetExpand}
onViewInProjectGraph={handleViewInProjectGraph}
onViewInTaskGraph={handleViewInTaskGraph}
onRunTarget={environment === 'nx-console' ? handleRunTarget : undefined}
/>
<ErrorToast errors={props.errors} />
</>
);
}

Expand Down
1 change: 1 addition & 0 deletions graph/shared/src/index.ts
Expand Up @@ -6,3 +6,4 @@ export * from './lib/use-route-constructor';
export * from './lib/use-interval-when';
export * from './lib/project-graph-data-service/get-project-graph-data-service';
export * from './lib/fetch-project-graph';
export * from './lib/error-toast';

0 comments on commit 59b139e

Please sign in to comment.