Add Exceptions feature with routing, layout, and side menu integration

This commit is contained in:
Nawaz Dhandala 2026-01-08 19:14:42 +00:00
parent e29b9ce00d
commit c562af4d90
No known key found for this signature in database
GPG key ID: 96C5DCA24769DBCA
16 changed files with 462 additions and 146 deletions

View file

@ -178,6 +178,11 @@ const AIAgentTasksRoutes: React.LazyExoticComponent<
> = lazy(() => {
return import("./Routes/AIAgentTasksRoutes");
});
const ExceptionsRoutes: React.LazyExoticComponent<
React.FunctionComponent<PageComponentProps>
> = lazy(() => {
return import("./Routes/ExceptionsRoutes");
});
const PageNotFound: React.LazyExoticComponent<
React.FunctionComponent<PageComponentProps>
> = lazy(() => {
@ -610,6 +615,13 @@ const App: () => JSX.Element = () => {
element={<AIAgentTasksRoutes {...commonPageProps} />}
/>
{/** Exceptions */}
<PageRoute
path={RouteMap[PageMap.EXCEPTIONS_ROOT]?.toString() || ""}
element={<ExceptionsRoutes {...commonPageProps} />}
/>
{/* 👇️ only match this when no other routes match */}
<PageRoute
path="*"

View file

@ -92,6 +92,15 @@ const DashboardNavbar: FunctionComponent<ComponentProps> = (
icon: IconProp.Brain,
iconColor: "violet",
},
{
title: "Exceptions",
description: "Track and manage exceptions.",
route: RouteUtil.populateRouteParams(
RouteMap[PageMap.EXCEPTIONS] as Route,
),
icon: IconProp.Alert,
iconColor: "red",
},
{
title: "Service Catalog",
description: "Manage services and dependencies.",

View file

@ -0,0 +1,29 @@
import PageComponentProps from "../PageComponentProps";
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
import React, { FunctionComponent, ReactElement } from "react";
import ExceptionsTable from "../../Components/Exceptions/ExceptionsTable";
const ArchivedExceptionsPage: FunctionComponent<PageComponentProps> = (
props: PageComponentProps,
): ReactElement => {
const disableTelemetryForThisProject: boolean =
props.currentProject?.reseller?.enableTelemetryFeatures === false;
if (disableTelemetryForThisProject) {
return (
<ErrorMessage message="Looks like you have bought this plan from a reseller. It did not include telemetry features in your plan. Telemetry features are disabled for this project." />
);
}
return (
<ExceptionsTable
query={{
isArchived: true,
}}
title="Archived Exceptions"
description="All the exceptions that have been archived. You will not be notified about these exceptions."
/>
);
};
export default ArchivedExceptionsPage;

View file

@ -0,0 +1,35 @@
import { getExceptionsBreadcrumbs } from "../../Utils/Breadcrumbs";
import PageMap from "../../Utils/PageMap";
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
import PageComponentProps from "../PageComponentProps";
import SideMenu from "./SideMenu";
import Page from "Common/UI/Components/Page/Page";
import Navigation from "Common/UI/Utils/Navigation";
import React, { FunctionComponent, ReactElement } from "react";
import { Outlet } from "react-router-dom";
const ExceptionsLayout: FunctionComponent<
PageComponentProps
> = (): ReactElement => {
const path: string = Navigation.getRoutePath(RouteUtil.getRoutes());
if (path.endsWith("exceptions") || path.endsWith("exceptions/*")) {
Navigation.navigate(
RouteUtil.populateRouteParams(RouteMap[PageMap.EXCEPTIONS_UNRESOLVED]!),
);
return <></>;
}
return (
<Page
title="Exceptions"
breadcrumbLinks={getExceptionsBreadcrumbs(path)}
sideMenu={<SideMenu />}
>
<Outlet />
</Page>
);
};
export default ExceptionsLayout;

View file

@ -0,0 +1,30 @@
import PageComponentProps from "../PageComponentProps";
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
import React, { FunctionComponent, ReactElement } from "react";
import ExceptionsTable from "../../Components/Exceptions/ExceptionsTable";
const ResolvedExceptionsPage: FunctionComponent<PageComponentProps> = (
props: PageComponentProps,
): ReactElement => {
const disableTelemetryForThisProject: boolean =
props.currentProject?.reseller?.enableTelemetryFeatures === false;
if (disableTelemetryForThisProject) {
return (
<ErrorMessage message="Looks like you have bought this plan from a reseller. It did not include telemetry features in your plan. Telemetry features are disabled for this project." />
);
}
return (
<ExceptionsTable
query={{
isResolved: true,
isArchived: false,
}}
title="Resolved Exceptions"
description="All the exceptions that have been resolved."
/>
);
};
export default ResolvedExceptionsPage;

View file

@ -0,0 +1,59 @@
import TelemetryException from "Common/Models/DatabaseModels/TelemetryException";
import PageMap from "../../Utils/PageMap";
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
import Route from "Common/Types/API/Route";
import IconProp from "Common/Types/Icon/IconProp";
import SideMenu, {
SideMenuSectionProps,
} from "Common/UI/Components/SideMenu/SideMenu";
import React, { FunctionComponent, ReactElement } from "react";
import { BadgeType } from "Common/UI/Components/Badge/Badge";
import ProjectUtil from "Common/UI/Utils/Project";
const DashboardSideMenu: FunctionComponent = (): ReactElement => {
const sections: SideMenuSectionProps[] = [
{
title: "Exceptions",
items: [
{
link: {
title: "Unresolved",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.EXCEPTIONS_UNRESOLVED] as Route,
),
},
badgeType: BadgeType.DANGER,
icon: IconProp.Alert,
modelType: TelemetryException,
countQuery: {
projectId: ProjectUtil.getCurrentProjectId()!,
isResolved: false,
isArchived: false,
} as any,
},
{
link: {
title: "Resolved",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.EXCEPTIONS_RESOLVED] as Route,
),
},
icon: IconProp.Check,
},
{
link: {
title: "Archived",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.EXCEPTIONS_ARCHIVED] as Route,
),
},
icon: IconProp.Archive,
},
],
},
];
return <SideMenu sections={sections} />;
};
export default DashboardSideMenu;

View file

@ -0,0 +1,30 @@
import PageComponentProps from "../PageComponentProps";
import ErrorMessage from "Common/UI/Components/ErrorMessage/ErrorMessage";
import React, { FunctionComponent, ReactElement } from "react";
import ExceptionsTable from "../../Components/Exceptions/ExceptionsTable";
const UnresolvedExceptionsPage: FunctionComponent<PageComponentProps> = (
props: PageComponentProps,
): ReactElement => {
const disableTelemetryForThisProject: boolean =
props.currentProject?.reseller?.enableTelemetryFeatures === false;
if (disableTelemetryForThisProject) {
return (
<ErrorMessage message="Looks like you have bought this plan from a reseller. It did not include telemetry features in your plan. Telemetry features are disabled for this project." />
);
}
return (
<ExceptionsTable
query={{
isResolved: false,
isArchived: false,
}}
title="Unresolved Exceptions"
description="All the exceptions that have not been resolved."
/>
);
};
export default UnresolvedExceptionsPage;

View file

@ -0,0 +1,19 @@
import Navigation from "Common/UI/Utils/Navigation";
import ExceptionExplorer from "../../../Components/Exceptions/ExceptionExplorer";
import PageComponentProps from "../../PageComponentProps";
import React, { Fragment, FunctionComponent, ReactElement } from "react";
import ObjectID from "Common/Types/ObjectID";
const ExceptionViewPage: FunctionComponent<
PageComponentProps
> = (): ReactElement => {
const exceptionId: string = Navigation.getLastParamAsString(0);
return (
<Fragment>
<ExceptionExplorer telemetryExceptionId={new ObjectID(exceptionId)} />
</Fragment>
);
};
export default ExceptionViewPage;

View file

@ -0,0 +1,35 @@
import { getExceptionsBreadcrumbs } from "../../../Utils/Breadcrumbs";
import RouteMap, { RouteUtil } from "../../../Utils/RouteMap";
import PageComponentProps from "../../PageComponentProps";
import Page from "Common/UI/Components/Page/Page";
import Navigation from "Common/UI/Utils/Navigation";
import React, { FunctionComponent, ReactElement } from "react";
import { Outlet } from "react-router-dom";
import PageMap from "../../../Utils/PageMap";
const ExceptionViewLayout: FunctionComponent<
PageComponentProps
> = (): ReactElement => {
const path: string = Navigation.getRoutePath(RouteUtil.getRoutes());
if (path.endsWith("exceptions")) {
Navigation.navigate(
RouteUtil.populateRouteParams(
RouteMap[PageMap.EXCEPTIONS_UNRESOLVED]!,
),
);
return <></>;
}
return (
<Page
title="Exception Explorer"
breadcrumbLinks={getExceptionsBreadcrumbs(path)}
>
<Outlet />
</Page>
);
};
export default ExceptionViewLayout;

View file

@ -1,4 +1,3 @@
import TelemetryException from "Common/Models/DatabaseModels/TelemetryException";
import PageMap from "../../Utils/PageMap";
import RouteMap, { RouteUtil } from "../../Utils/RouteMap";
import Route from "Common/Types/API/Route";
@ -7,8 +6,6 @@ import SideMenu, {
SideMenuSectionProps,
} from "Common/UI/Components/SideMenu/SideMenu";
import React, { FunctionComponent, ReactElement } from "react";
import { BadgeType } from "Common/UI/Components/Badge/Badge";
import ProjectUtil from "Common/UI/Utils/Project";
const DashboardSideMenu: FunctionComponent = (): ReactElement => {
const sections: SideMenuSectionProps[] = [
@ -67,45 +64,6 @@ const DashboardSideMenu: FunctionComponent = (): ReactElement => {
},
],
},
{
title: "Exceptions",
items: [
{
link: {
title: "Unresolved",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.TELEMETRY_EXCEPTIONS_UNRESOLVED] as Route,
),
},
badgeType: BadgeType.DANGER,
icon: IconProp.Alert,
modelType: TelemetryException,
countQuery: {
projectId: ProjectUtil.getCurrentProjectId()!,
isResolved: false,
isArchived: false,
} as any,
},
{
link: {
title: "Resolved",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.TELEMETRY_EXCEPTIONS_RESOLVED] as Route,
),
},
icon: IconProp.Check,
},
{
link: {
title: "Archived",
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.TELEMETRY_EXCEPTIONS_ARCHIVED] as Route,
),
},
icon: IconProp.Archive,
},
],
},
];
return <SideMenu sections={sections} />;

View file

@ -0,0 +1,116 @@
import Loader from "../Components/Loader/Loader";
import ComponentProps from "../Pages/PageComponentProps";
import ExceptionsLayout from "../Pages/Exceptions/Layout";
import ExceptionViewLayout from "../Pages/Exceptions/View/Layout";
import PageMap from "../Utils/PageMap";
import RouteMap, { ExceptionsRoutePath } from "../Utils/RouteMap";
import Route from "Common/Types/API/Route";
import React, {
FunctionComponent,
LazyExoticComponent,
ReactElement,
Suspense,
lazy,
} from "react";
import { Route as PageRoute, Routes } from "react-router-dom";
// Lazy Pages
const ExceptionsUnresolved: LazyExoticComponent<
FunctionComponent<ComponentProps>
> = lazy(() => {
return import("../Pages/Exceptions/Unresolved");
});
const ExceptionsResolved: LazyExoticComponent<
FunctionComponent<ComponentProps>
> = lazy(() => {
return import("../Pages/Exceptions/Resolved");
});
const ExceptionsArchived: LazyExoticComponent<
FunctionComponent<ComponentProps>
> = lazy(() => {
return import("../Pages/Exceptions/Archived");
});
const ExceptionView: LazyExoticComponent<FunctionComponent<ComponentProps>> =
lazy(() => {
return import("../Pages/Exceptions/View/Index");
});
const ExceptionsRoutes: FunctionComponent<ComponentProps> = (
props: ComponentProps,
): ReactElement => {
return (
<Routes>
<PageRoute path="/" element={<ExceptionsLayout {...props} />}>
<PageRoute
index
element={
<Suspense fallback={Loader}>
<ExceptionsUnresolved
{...props}
pageRoute={RouteMap[PageMap.EXCEPTIONS] as Route}
/>
</Suspense>
}
/>
<PageRoute
path={ExceptionsRoutePath[PageMap.EXCEPTIONS_UNRESOLVED] || ""}
element={
<Suspense fallback={Loader}>
<ExceptionsUnresolved
{...props}
pageRoute={RouteMap[PageMap.EXCEPTIONS_UNRESOLVED] as Route}
/>
</Suspense>
}
/>
<PageRoute
path={ExceptionsRoutePath[PageMap.EXCEPTIONS_RESOLVED] || ""}
element={
<Suspense fallback={Loader}>
<ExceptionsResolved
{...props}
pageRoute={RouteMap[PageMap.EXCEPTIONS_RESOLVED] as Route}
/>
</Suspense>
}
/>
<PageRoute
path={ExceptionsRoutePath[PageMap.EXCEPTIONS_ARCHIVED] || ""}
element={
<Suspense fallback={Loader}>
<ExceptionsArchived
{...props}
pageRoute={RouteMap[PageMap.EXCEPTIONS_ARCHIVED] as Route}
/>
</Suspense>
}
/>
<PageRoute
path={ExceptionsRoutePath[PageMap.EXCEPTIONS_VIEW] || ""}
element={<ExceptionViewLayout {...props} />}
>
<PageRoute
index
element={
<Suspense fallback={Loader}>
<ExceptionView
{...props}
pageRoute={RouteMap[PageMap.EXCEPTIONS_VIEW] as Route}
/>
</Suspense>
}
/>
</PageRoute>
</PageRoute>
</Routes>
);
};
export default ExceptionsRoutes;

View file

@ -2,7 +2,6 @@ import Loader from "../Components/Loader/Loader";
import ComponentProps from "../Pages/PageComponentProps";
import TelemetryServiceViewLayout from "../Pages/Telemetry/Services/View/Layout";
import TelemetryMetricLayout from "../Pages/Telemetry/Metrics/View/Layout";
import TelemetryExceptionViewLayout from "../Pages/Telemetry/Exceptions/View/Layout";
import TelemetryTraceLayout from "../Pages/Telemetry/Traces/View/Layout";
import TelemetryViewLayout from "../Pages/Telemetry/Layout";
import PageMap from "../Utils/PageMap";
@ -46,36 +45,12 @@ const TelemetryTraces: LazyExoticComponent<FunctionComponent<ComponentProps>> =
return import("../Pages/Telemetry/Traces");
});
const TelemetryExceptionsUnresolved: LazyExoticComponent<
FunctionComponent<ComponentProps>
> = lazy(() => {
return import("../Pages/Telemetry/Exceptions/Unresolved");
});
const TelemetryExceptionsResolved: LazyExoticComponent<
FunctionComponent<ComponentProps>
> = lazy(() => {
return import("../Pages/Telemetry/Exceptions/Resolved");
});
const TelemetryServiceViewException: LazyExoticComponent<
FunctionComponent<ComponentProps>
> = lazy(() => {
return import("../Pages/Telemetry/Services/View/Exceptions/View/Index");
});
const TelemetryExceptionsArchived: LazyExoticComponent<
FunctionComponent<ComponentProps>
> = lazy(() => {
return import("../Pages/Telemetry/Exceptions/Archived");
});
const TelemetryViewException: LazyExoticComponent<
FunctionComponent<ComponentProps>
> = lazy(() => {
return import("../Pages/Telemetry/Exceptions/View/Index");
});
const TelemetryMetrics: LazyExoticComponent<FunctionComponent<ComponentProps>> =
lazy(() => {
return import("../Pages/Telemetry/Metrics");
@ -213,54 +188,6 @@ const TelemetryRoutes: FunctionComponent<ComponentProps> = (
}
/>
{/** Exceptions - Unresolved, Resolved, Archived */}
<PageRoute
path={
TelemetryRoutePath[PageMap.TELEMETRY_EXCEPTIONS_UNRESOLVED] || ""
}
element={
<Suspense fallback={Loader}>
<TelemetryExceptionsUnresolved
{...props}
pageRoute={
RouteMap[PageMap.TELEMETRY_EXCEPTIONS_UNRESOLVED] as Route
}
/>
</Suspense>
}
/>
<PageRoute
path={TelemetryRoutePath[PageMap.TELEMETRY_EXCEPTIONS_RESOLVED] || ""}
element={
<Suspense fallback={Loader}>
<TelemetryExceptionsResolved
{...props}
pageRoute={
RouteMap[PageMap.TELEMETRY_EXCEPTIONS_RESOLVED] as Route
}
/>
</Suspense>
}
/>
<PageRoute
path={TelemetryRoutePath[PageMap.TELEMETRY_EXCEPTIONS_ARCHIVED] || ""}
element={
<Suspense fallback={Loader}>
<TelemetryExceptionsArchived
{...props}
pageRoute={
RouteMap[PageMap.TELEMETRY_EXCEPTIONS_ARCHIVED] as Route
}
/>
</Suspense>
}
/>
{/** ---- */}
<PageRoute
path={TelemetryRoutePath[PageMap.TELEMETRY_SERVICES] || ""}
element={
@ -317,37 +244,6 @@ const TelemetryRoutes: FunctionComponent<ComponentProps> = (
/>
</PageRoute>
{/** Exception View */}
<PageRoute
path={TelemetryRoutePath[PageMap.TELEMETRY_EXCEPTIONS_ROOT] || ""}
element={<TelemetryExceptionViewLayout {...props} />}
>
<PageRoute
index
element={
<Suspense fallback={Loader}>
<TelemetryExceptionsUnresolved
{...props}
pageRoute={RouteMap[PageMap.TELEMETRY_EXCEPTIONS_ROOT] as Route}
/>
</Suspense>
}
/>
<PageRoute
path={RouteUtil.getLastPathForKey(PageMap.TELEMETRY_EXCEPTIONS_VIEW)}
element={
<Suspense fallback={Loader}>
<TelemetryViewException
{...props}
pageRoute={RouteMap[PageMap.TELEMETRY_EXCEPTIONS_VIEW] as Route}
/>
</Suspense>
}
/>
</PageRoute>
{/* Trace View */}
<PageRoute

View file

@ -0,0 +1,36 @@
import PageMap from "../PageMap";
import { BuildBreadcrumbLinksByTitles } from "./Helper";
import Dictionary from "Common/Types/Dictionary";
import Link from "Common/Types/Link";
export function getExceptionsBreadcrumbs(
path: string,
): Array<Link> | undefined {
const breadcrumpLinksMap: Dictionary<Link[]> = {
...BuildBreadcrumbLinksByTitles(PageMap.EXCEPTIONS, [
"Project",
"Exceptions",
]),
...BuildBreadcrumbLinksByTitles(PageMap.EXCEPTIONS_UNRESOLVED, [
"Project",
"Exceptions",
"Unresolved",
]),
...BuildBreadcrumbLinksByTitles(PageMap.EXCEPTIONS_RESOLVED, [
"Project",
"Exceptions",
"Resolved",
]),
...BuildBreadcrumbLinksByTitles(PageMap.EXCEPTIONS_ARCHIVED, [
"Project",
"Exceptions",
"Archived",
]),
...BuildBreadcrumbLinksByTitles(PageMap.EXCEPTIONS_VIEW, [
"Project",
"Exceptions",
"View Exception",
]),
};
return breadcrumpLinksMap[path];
}

View file

@ -11,3 +11,4 @@ export * from "./ServiceCatalogBreadcrumbs";
export * from "./CodeRepositoryBreadcrumbs";
export * from "./DashboardBreadCrumbs";
export * from "./AIAgentTasksBreadcrumbs";
export * from "./ExceptionsBreadcrumbs";

View file

@ -430,6 +430,14 @@ enum PageMap {
AI_AGENT_TASK_VIEW_PULL_REQUESTS = "AI_AGENT_TASK_VIEW_PULL_REQUESTS",
AI_AGENT_TASK_VIEW_DELETE = "AI_AGENT_TASK_VIEW_DELETE",
// Exceptions (standalone, not under Telemetry)
EXCEPTIONS_ROOT = "EXCEPTIONS_ROOT",
EXCEPTIONS = "EXCEPTIONS",
EXCEPTIONS_UNRESOLVED = "EXCEPTIONS_UNRESOLVED",
EXCEPTIONS_RESOLVED = "EXCEPTIONS_RESOLVED",
EXCEPTIONS_ARCHIVED = "EXCEPTIONS_ARCHIVED",
EXCEPTIONS_VIEW = "EXCEPTIONS_VIEW",
// Push Logs in resource views
}

View file

@ -113,6 +113,14 @@ export const TelemetryRoutePath: Dictionary<string> = {
[PageMap.TELEMETRY_SERVICES_VIEW_EXCEPTIONS_RESOLVED]: `services/${RouteParams.ModelID}/exceptions/resolved`,
};
export const ExceptionsRoutePath: Dictionary<string> = {
[PageMap.EXCEPTIONS]: "unresolved",
[PageMap.EXCEPTIONS_UNRESOLVED]: "unresolved",
[PageMap.EXCEPTIONS_RESOLVED]: "resolved",
[PageMap.EXCEPTIONS_ARCHIVED]: "archived",
[PageMap.EXCEPTIONS_VIEW]: `${RouteParams.ModelID}`,
};
export const DashboardsRoutePath: Dictionary<string> = {
[PageMap.DASHBOARD_VIEW]: `${RouteParams.ModelID}`,
[PageMap.DASHBOARD_VIEW_OVERVIEW]: `${RouteParams.ModelID}/overview`,
@ -2145,6 +2153,41 @@ const RouteMap: Dictionary<Route> = {
AIAgentTasksRoutePath[PageMap.AI_AGENT_TASK_VIEW_DELETE]
}`,
),
// Exceptions (standalone menu item)
[PageMap.EXCEPTIONS_ROOT]: new Route(
`/dashboard/${RouteParams.ProjectID}/exceptions/*`,
),
[PageMap.EXCEPTIONS]: new Route(
`/dashboard/${RouteParams.ProjectID}/exceptions/${
ExceptionsRoutePath[PageMap.EXCEPTIONS]
}`,
),
[PageMap.EXCEPTIONS_UNRESOLVED]: new Route(
`/dashboard/${RouteParams.ProjectID}/exceptions/${
ExceptionsRoutePath[PageMap.EXCEPTIONS_UNRESOLVED]
}`,
),
[PageMap.EXCEPTIONS_RESOLVED]: new Route(
`/dashboard/${RouteParams.ProjectID}/exceptions/${
ExceptionsRoutePath[PageMap.EXCEPTIONS_RESOLVED]
}`,
),
[PageMap.EXCEPTIONS_ARCHIVED]: new Route(
`/dashboard/${RouteParams.ProjectID}/exceptions/${
ExceptionsRoutePath[PageMap.EXCEPTIONS_ARCHIVED]
}`,
),
[PageMap.EXCEPTIONS_VIEW]: new Route(
`/dashboard/${RouteParams.ProjectID}/exceptions/${
ExceptionsRoutePath[PageMap.EXCEPTIONS_VIEW]
}`,
),
};
export class RouteUtil {