feat(graph): add error boundary error page for project details (#22007)
Co-authored-by: Nicholas Cunningham <ndcunningham@gmail.com>
This commit is contained in:
parent
e3dc02a1ad
commit
a3ef2c6c3f
@ -5,12 +5,15 @@ import { Shell } from './shell';
|
|||||||
/* eslint-disable @nx/enforce-module-boundaries */
|
/* eslint-disable @nx/enforce-module-boundaries */
|
||||||
// nx-ignore-next-line
|
// nx-ignore-next-line
|
||||||
import { ProjectGraphClientResponse } from 'nx/src/command-line/graph/graph';
|
import { ProjectGraphClientResponse } from 'nx/src/command-line/graph/graph';
|
||||||
|
// nx-ignore-next-line
|
||||||
|
import type { ProjectGraphProjectNode } from 'nx/src/config/project-graph';
|
||||||
import {
|
import {
|
||||||
getEnvironmentConfig,
|
getEnvironmentConfig,
|
||||||
getProjectGraphDataService,
|
getProjectGraphDataService,
|
||||||
} from '@nx/graph/shared';
|
} from '@nx/graph/shared';
|
||||||
import { TasksSidebarErrorBoundary } from './feature-tasks/tasks-sidebar-error-boundary';
|
import { TasksSidebarErrorBoundary } from './feature-tasks/tasks-sidebar-error-boundary';
|
||||||
import { ProjectDetailsPage } from '@nx/graph/project-details';
|
import { ProjectDetailsPage } from '@nx/graph/project-details';
|
||||||
|
import { ErrorBoundary } from './ui-components/error-boundary';
|
||||||
|
|
||||||
const { appConfig } = getEnvironmentConfig();
|
const { appConfig } = getEnvironmentConfig();
|
||||||
const projectGraphDataService = getProjectGraphDataService();
|
const projectGraphDataService = getProjectGraphDataService();
|
||||||
@ -71,7 +74,11 @@ const sourceMapsLoader = async (selectedWorkspaceId: string) => {
|
|||||||
const projectDetailsLoader = async (
|
const projectDetailsLoader = async (
|
||||||
selectedWorkspaceId: string,
|
selectedWorkspaceId: string,
|
||||||
projectName: string
|
projectName: string
|
||||||
) => {
|
): Promise<{
|
||||||
|
hash: string;
|
||||||
|
project: ProjectGraphProjectNode;
|
||||||
|
sourceMap: Record<string, string[]>;
|
||||||
|
}> => {
|
||||||
const workspaceData = await workspaceDataLoader(selectedWorkspaceId);
|
const workspaceData = await workspaceDataLoader(selectedWorkspaceId);
|
||||||
const sourceMaps = await sourceMapsLoader(selectedWorkspaceId);
|
const sourceMaps = await sourceMapsLoader(selectedWorkspaceId);
|
||||||
|
|
||||||
@ -152,6 +159,7 @@ const childRoutes: RouteObject[] = [
|
|||||||
export const devRoutes: RouteObject[] = [
|
export const devRoutes: RouteObject[] = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
|
errorElement: <ErrorBoundary />,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
index: true,
|
index: true,
|
||||||
@ -181,7 +189,7 @@ export const devRoutes: RouteObject[] = [
|
|||||||
path: ':selectedWorkspaceId/project-details/:projectName',
|
path: ':selectedWorkspaceId/project-details/:projectName',
|
||||||
id: 'selectedProjectDetails',
|
id: 'selectedProjectDetails',
|
||||||
element: <ProjectDetailsPage />,
|
element: <ProjectDetailsPage />,
|
||||||
loader: async ({ request, params }) => {
|
loader: async ({ params }) => {
|
||||||
const projectName = params.projectName;
|
const projectName = params.projectName;
|
||||||
return projectDetailsLoader(params.selectedWorkspaceId, projectName);
|
return projectDetailsLoader(params.selectedWorkspaceId, projectName);
|
||||||
},
|
},
|
||||||
@ -194,7 +202,7 @@ export const releaseRoutes: RouteObject[] = [
|
|||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
id: 'selectedWorkspace',
|
id: 'selectedWorkspace',
|
||||||
loader: async ({ request, params }) => {
|
loader: async () => {
|
||||||
const selectedWorkspaceId = appConfig.defaultWorkspaceId;
|
const selectedWorkspaceId = appConfig.defaultWorkspaceId;
|
||||||
return workspaceDataLoader(selectedWorkspaceId);
|
return workspaceDataLoader(selectedWorkspaceId);
|
||||||
},
|
},
|
||||||
@ -213,12 +221,14 @@ export const releaseRoutes: RouteObject[] = [
|
|||||||
},
|
},
|
||||||
...childRoutes,
|
...childRoutes,
|
||||||
],
|
],
|
||||||
|
errorElement: <ErrorBoundary />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'project-details/:projectName',
|
path: 'project-details/:projectName',
|
||||||
id: 'selectedProjectDetails',
|
id: 'selectedProjectDetails',
|
||||||
element: <ProjectDetailsPage />,
|
element: <ProjectDetailsPage />,
|
||||||
loader: async ({ request, params }) => {
|
errorElement: <ErrorBoundary />,
|
||||||
|
loader: async ({ params }) => {
|
||||||
const projectName = params.projectName;
|
const projectName = params.projectName;
|
||||||
return projectDetailsLoader(appConfig.defaultWorkspaceId, projectName);
|
return projectDetailsLoader(appConfig.defaultWorkspaceId, projectName);
|
||||||
},
|
},
|
||||||
|
|||||||
27
graph/client/src/app/ui-components/error-boundary.tsx
Normal file
27
graph/client/src/app/ui-components/error-boundary.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { useEnvironmentConfig } from '@nx/graph/shared';
|
||||||
|
import { ProjectDetailsHeader } from 'graph/project-details/src/lib/project-details-header';
|
||||||
|
import { useRouteError } from 'react-router-dom';
|
||||||
|
|
||||||
|
export function ErrorBoundary() {
|
||||||
|
let error = useRouteError()?.toString();
|
||||||
|
console.error(error);
|
||||||
|
const environment = useEnvironmentConfig()?.environment;
|
||||||
|
|
||||||
|
let message = 'Disconnected from graph server. ';
|
||||||
|
if (environment === 'nx-console') {
|
||||||
|
message += 'Please refresh the page.';
|
||||||
|
} else {
|
||||||
|
message += 'Please rerun your command and refresh the page.';
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
25
graph/project-details/src/lib/project-details-header.tsx
Normal file
25
graph/project-details/src/lib/project-details-header.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useRouteConstructor } from '@nx/graph/shared';
|
||||||
|
import { ThemePanel } from '@nx/graph/ui-theme';
|
||||||
|
|
||||||
|
export function ProjectDetailsHeader() {
|
||||||
|
const routeConstructor = useRouteConstructor();
|
||||||
|
return (
|
||||||
|
<header className="flex w-full justify-center items-center py-2 mx-auto border-b-2 border-slate-900/10 mb-8 dark:border-slate-300/10">
|
||||||
|
<div className="flex flex-grow items-center justify-between max-w-6xl px-8 ">
|
||||||
|
<Link to={routeConstructor('/projects', false)}>
|
||||||
|
<svg
|
||||||
|
className="h-10 w-auto text-slate-900 dark:text-white"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<title>Nx</title>
|
||||||
|
<path d="M11.987 14.138l-3.132 4.923-5.193-8.427-.012 8.822H0V4.544h3.691l5.247 8.833.005-3.998 3.044 4.759zm.601-5.761c.024-.048 0-3.784.008-3.833h-3.65c.002.059-.005 3.776-.003 3.833h3.645zm5.634 4.134a2.061 2.061 0 0 0-1.969 1.336 1.963 1.963 0 0 1 2.343-.739c.396.161.917.422 1.33.283a2.1 2.1 0 0 0-1.704-.88zm3.39 1.061c-.375-.13-.8-.277-1.109-.681-.06-.08-.116-.17-.176-.265a2.143 2.143 0 0 0-.533-.642c-.294-.216-.68-.322-1.18-.322a2.482 2.482 0 0 0-2.294 1.536 2.325 2.325 0 0 1 4.002.388.75.75 0 0 0 .836.334c.493-.105.46.36 1.203.518v-.133c-.003-.446-.246-.55-.75-.733zm2.024 1.266a.723.723 0 0 0 .347-.638c-.01-2.957-2.41-5.487-5.37-5.487a5.364 5.364 0 0 0-4.487 2.418c-.01-.026-1.522-2.39-1.538-2.418H8.943l3.463 5.423-3.379 5.32h3.54l1.54-2.366 1.568 2.366h3.541l-3.21-5.052a.7.7 0 0 1-.084-.32 2.69 2.69 0 0 1 2.69-2.691h.001c1.488 0 1.736.89 2.057 1.308.634.826 1.9.464 1.9 1.541a.707.707 0 0 0 1.066.596zm.35.133c-.173.372-.56.338-.755.639-.176.271.114.412.114.412s.337.156.538-.311c.104-.231.14-.488.103-.74z" />
|
||||||
|
</svg>
|
||||||
|
</Link>
|
||||||
|
<ThemePanel />
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -2,7 +2,6 @@
|
|||||||
// nx-ignore-next-line
|
// nx-ignore-next-line
|
||||||
import { ProjectGraphProjectNode } from '@nx/devkit';
|
import { ProjectGraphProjectNode } from '@nx/devkit';
|
||||||
import {
|
import {
|
||||||
Link,
|
|
||||||
ScrollRestoration,
|
ScrollRestoration,
|
||||||
useParams,
|
useParams,
|
||||||
useRouteLoaderData,
|
useRouteLoaderData,
|
||||||
@ -13,9 +12,8 @@ import {
|
|||||||
getProjectGraphDataService,
|
getProjectGraphDataService,
|
||||||
useEnvironmentConfig,
|
useEnvironmentConfig,
|
||||||
useIntervalWhen,
|
useIntervalWhen,
|
||||||
useRouteConstructor,
|
|
||||||
} from '@nx/graph/shared';
|
} from '@nx/graph/shared';
|
||||||
import { ThemePanel } from '@nx/graph/ui-theme';
|
import { ProjectDetailsHeader } from './project-details-header';
|
||||||
|
|
||||||
export function ProjectDetailsPage() {
|
export function ProjectDetailsPage() {
|
||||||
const { project, sourceMap, hash } = useRouteLoaderData(
|
const { project, sourceMap, hash } = useRouteLoaderData(
|
||||||
@ -46,28 +44,11 @@ export function ProjectDetailsPage() {
|
|||||||
watch
|
watch
|
||||||
);
|
);
|
||||||
|
|
||||||
const routeConstructor = useRouteConstructor();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-center w-full text-slate-700 dark:text-slate-400">
|
<div className="flex flex-col justify-center w-full text-slate-700 dark:text-slate-400">
|
||||||
<ScrollRestoration />
|
<ScrollRestoration />
|
||||||
{environment !== 'nx-console' ? (
|
{environment !== 'nx-console' ? (
|
||||||
<header className="flex w-full justify-center items-center py-2 mx-auto border-b-2 border-slate-900/10 mb-8 dark:border-slate-300/10">
|
<ProjectDetailsHeader />
|
||||||
<div className="flex flex-grow items-center justify-between max-w-6xl px-8 ">
|
|
||||||
<Link to={routeConstructor('/projects', false)}>
|
|
||||||
<svg
|
|
||||||
className="h-10 w-auto text-slate-900 dark:text-white"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="currentColor"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<title>Nx</title>
|
|
||||||
<path d="M11.987 14.138l-3.132 4.923-5.193-8.427-.012 8.822H0V4.544h3.691l5.247 8.833.005-3.998 3.044 4.759zm.601-5.761c.024-.048 0-3.784.008-3.833h-3.65c.002.059-.005 3.776-.003 3.833h3.645zm5.634 4.134a2.061 2.061 0 0 0-1.969 1.336 1.963 1.963 0 0 1 2.343-.739c.396.161.917.422 1.33.283a2.1 2.1 0 0 0-1.704-.88zm3.39 1.061c-.375-.13-.8-.277-1.109-.681-.06-.08-.116-.17-.176-.265a2.143 2.143 0 0 0-.533-.642c-.294-.216-.68-.322-1.18-.322a2.482 2.482 0 0 0-2.294 1.536 2.325 2.325 0 0 1 4.002.388.75.75 0 0 0 .836.334c.493-.105.46.36 1.203.518v-.133c-.003-.446-.246-.55-.75-.733zm2.024 1.266a.723.723 0 0 0 .347-.638c-.01-2.957-2.41-5.487-5.37-5.487a5.364 5.364 0 0 0-4.487 2.418c-.01-.026-1.522-2.39-1.538-2.418H8.943l3.463 5.423-3.379 5.32h3.54l1.54-2.366 1.568 2.366h3.541l-3.21-5.052a.7.7 0 0 1-.084-.32 2.69 2.69 0 0 1 2.69-2.691h.001c1.488 0 1.736.89 2.057 1.308.634.826 1.9.464 1.9 1.541a.707.707 0 0 0 1.066.596zm.35.133c-.173.372-.56.338-.755.639-.176.271.114.412.114.412s.337.156.538-.311c.104-.231.14-.488.103-.74z" />
|
|
||||||
</svg>
|
|
||||||
</Link>
|
|
||||||
<ThemePanel />
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="py-2"></div>
|
<div className="py-2"></div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -4,9 +4,7 @@ import {
|
|||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
ChevronUpIcon,
|
ChevronUpIcon,
|
||||||
EyeIcon,
|
EyeIcon,
|
||||||
InformationCircleIcon,
|
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
QuestionMarkCircleIcon,
|
|
||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
|
|
||||||
// nx-ignore-next-line
|
// nx-ignore-next-line
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user