diff --git a/Dashboard/src/App.tsx b/Dashboard/src/App.tsx index b748a253ed..52c6f1c708 100644 --- a/Dashboard/src/App.tsx +++ b/Dashboard/src/App.tsx @@ -178,6 +178,11 @@ const AIAgentTasksRoutes: React.LazyExoticComponent< > = lazy(() => { return import("./Routes/AIAgentTasksRoutes"); }); +const ExceptionsRoutes: React.LazyExoticComponent< + React.FunctionComponent +> = lazy(() => { + return import("./Routes/ExceptionsRoutes"); +}); const PageNotFound: React.LazyExoticComponent< React.FunctionComponent > = lazy(() => { @@ -610,6 +615,13 @@ const App: () => JSX.Element = () => { element={} /> + {/** Exceptions */} + + } + /> + {/* 👇️ only match this when no other routes match */} = ( 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.", diff --git a/Dashboard/src/Pages/Exceptions/Archived.tsx b/Dashboard/src/Pages/Exceptions/Archived.tsx new file mode 100644 index 0000000000..e8035d781f --- /dev/null +++ b/Dashboard/src/Pages/Exceptions/Archived.tsx @@ -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 = ( + props: PageComponentProps, +): ReactElement => { + const disableTelemetryForThisProject: boolean = + props.currentProject?.reseller?.enableTelemetryFeatures === false; + + if (disableTelemetryForThisProject) { + return ( + + ); + } + + return ( + + ); +}; + +export default ArchivedExceptionsPage; diff --git a/Dashboard/src/Pages/Exceptions/Layout.tsx b/Dashboard/src/Pages/Exceptions/Layout.tsx new file mode 100644 index 0000000000..97ade5b2cb --- /dev/null +++ b/Dashboard/src/Pages/Exceptions/Layout.tsx @@ -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 ( + } + > + + + ); +}; + +export default ExceptionsLayout; diff --git a/Dashboard/src/Pages/Exceptions/Resolved.tsx b/Dashboard/src/Pages/Exceptions/Resolved.tsx new file mode 100644 index 0000000000..68f6326340 --- /dev/null +++ b/Dashboard/src/Pages/Exceptions/Resolved.tsx @@ -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 = ( + props: PageComponentProps, +): ReactElement => { + const disableTelemetryForThisProject: boolean = + props.currentProject?.reseller?.enableTelemetryFeatures === false; + + if (disableTelemetryForThisProject) { + return ( + + ); + } + + return ( + + ); +}; + +export default ResolvedExceptionsPage; diff --git a/Dashboard/src/Pages/Exceptions/SideMenu.tsx b/Dashboard/src/Pages/Exceptions/SideMenu.tsx new file mode 100644 index 0000000000..85964e7730 --- /dev/null +++ b/Dashboard/src/Pages/Exceptions/SideMenu.tsx @@ -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 ; +}; + +export default DashboardSideMenu; diff --git a/Dashboard/src/Pages/Exceptions/Unresolved.tsx b/Dashboard/src/Pages/Exceptions/Unresolved.tsx new file mode 100644 index 0000000000..a97e695fdc --- /dev/null +++ b/Dashboard/src/Pages/Exceptions/Unresolved.tsx @@ -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 = ( + props: PageComponentProps, +): ReactElement => { + const disableTelemetryForThisProject: boolean = + props.currentProject?.reseller?.enableTelemetryFeatures === false; + + if (disableTelemetryForThisProject) { + return ( + + ); + } + + return ( + + ); +}; + +export default UnresolvedExceptionsPage; diff --git a/Dashboard/src/Pages/Exceptions/View/Index.tsx b/Dashboard/src/Pages/Exceptions/View/Index.tsx new file mode 100644 index 0000000000..cd957585c8 --- /dev/null +++ b/Dashboard/src/Pages/Exceptions/View/Index.tsx @@ -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 ( + + + + ); +}; + +export default ExceptionViewPage; diff --git a/Dashboard/src/Pages/Exceptions/View/Layout.tsx b/Dashboard/src/Pages/Exceptions/View/Layout.tsx new file mode 100644 index 0000000000..7b5ddb484a --- /dev/null +++ b/Dashboard/src/Pages/Exceptions/View/Layout.tsx @@ -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 ( + + + + ); +}; + +export default ExceptionViewLayout; diff --git a/Dashboard/src/Pages/Telemetry/SideMenu.tsx b/Dashboard/src/Pages/Telemetry/SideMenu.tsx index d24886802a..9327e77b70 100644 --- a/Dashboard/src/Pages/Telemetry/SideMenu.tsx +++ b/Dashboard/src/Pages/Telemetry/SideMenu.tsx @@ -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 ; diff --git a/Dashboard/src/Routes/ExceptionsRoutes.tsx b/Dashboard/src/Routes/ExceptionsRoutes.tsx new file mode 100644 index 0000000000..42d45a47e0 --- /dev/null +++ b/Dashboard/src/Routes/ExceptionsRoutes.tsx @@ -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 +> = lazy(() => { + return import("../Pages/Exceptions/Unresolved"); +}); + +const ExceptionsResolved: LazyExoticComponent< + FunctionComponent +> = lazy(() => { + return import("../Pages/Exceptions/Resolved"); +}); + +const ExceptionsArchived: LazyExoticComponent< + FunctionComponent +> = lazy(() => { + return import("../Pages/Exceptions/Archived"); +}); + +const ExceptionView: LazyExoticComponent> = + lazy(() => { + return import("../Pages/Exceptions/View/Index"); + }); + +const ExceptionsRoutes: FunctionComponent = ( + props: ComponentProps, +): ReactElement => { + return ( + + }> + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + } + > + + + + } + /> + + + + ); +}; + +export default ExceptionsRoutes; diff --git a/Dashboard/src/Routes/TelemetryRoutes.tsx b/Dashboard/src/Routes/TelemetryRoutes.tsx index c537daeab1..9e55f72129 100644 --- a/Dashboard/src/Routes/TelemetryRoutes.tsx +++ b/Dashboard/src/Routes/TelemetryRoutes.tsx @@ -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> = return import("../Pages/Telemetry/Traces"); }); -const TelemetryExceptionsUnresolved: LazyExoticComponent< - FunctionComponent -> = lazy(() => { - return import("../Pages/Telemetry/Exceptions/Unresolved"); -}); - -const TelemetryExceptionsResolved: LazyExoticComponent< - FunctionComponent -> = lazy(() => { - return import("../Pages/Telemetry/Exceptions/Resolved"); -}); - const TelemetryServiceViewException: LazyExoticComponent< FunctionComponent > = lazy(() => { return import("../Pages/Telemetry/Services/View/Exceptions/View/Index"); }); -const TelemetryExceptionsArchived: LazyExoticComponent< - FunctionComponent -> = lazy(() => { - return import("../Pages/Telemetry/Exceptions/Archived"); -}); - -const TelemetryViewException: LazyExoticComponent< - FunctionComponent -> = lazy(() => { - return import("../Pages/Telemetry/Exceptions/View/Index"); -}); - const TelemetryMetrics: LazyExoticComponent> = lazy(() => { return import("../Pages/Telemetry/Metrics"); @@ -213,54 +188,6 @@ const TelemetryRoutes: FunctionComponent = ( } /> - {/** Exceptions - Unresolved, Resolved, Archived */} - - - - - } - /> - - - - - } - /> - - - - - } - /> - - {/** ---- */} - = ( /> - {/** Exception View */} - - } - > - - - - } - /> - - - - - } - /> - - {/* Trace View */} | undefined { + const breadcrumpLinksMap: Dictionary = { + ...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]; +} diff --git a/Dashboard/src/Utils/Breadcrumbs/index.ts b/Dashboard/src/Utils/Breadcrumbs/index.ts index 3518f9b616..e70d5e750d 100644 --- a/Dashboard/src/Utils/Breadcrumbs/index.ts +++ b/Dashboard/src/Utils/Breadcrumbs/index.ts @@ -11,3 +11,4 @@ export * from "./ServiceCatalogBreadcrumbs"; export * from "./CodeRepositoryBreadcrumbs"; export * from "./DashboardBreadCrumbs"; export * from "./AIAgentTasksBreadcrumbs"; +export * from "./ExceptionsBreadcrumbs"; diff --git a/Dashboard/src/Utils/PageMap.ts b/Dashboard/src/Utils/PageMap.ts index e6fd809cb6..c30af40ae8 100644 --- a/Dashboard/src/Utils/PageMap.ts +++ b/Dashboard/src/Utils/PageMap.ts @@ -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 } diff --git a/Dashboard/src/Utils/RouteMap.ts b/Dashboard/src/Utils/RouteMap.ts index 9c9b2af966..03c385da0a 100644 --- a/Dashboard/src/Utils/RouteMap.ts +++ b/Dashboard/src/Utils/RouteMap.ts @@ -113,6 +113,14 @@ export const TelemetryRoutePath: Dictionary = { [PageMap.TELEMETRY_SERVICES_VIEW_EXCEPTIONS_RESOLVED]: `services/${RouteParams.ModelID}/exceptions/resolved`, }; +export const ExceptionsRoutePath: Dictionary = { + [PageMap.EXCEPTIONS]: "unresolved", + [PageMap.EXCEPTIONS_UNRESOLVED]: "unresolved", + [PageMap.EXCEPTIONS_RESOLVED]: "resolved", + [PageMap.EXCEPTIONS_ARCHIVED]: "archived", + [PageMap.EXCEPTIONS_VIEW]: `${RouteParams.ModelID}`, +}; + export const DashboardsRoutePath: Dictionary = { [PageMap.DASHBOARD_VIEW]: `${RouteParams.ModelID}`, [PageMap.DASHBOARD_VIEW_OVERVIEW]: `${RouteParams.ModelID}/overview`, @@ -2145,6 +2153,41 @@ const RouteMap: Dictionary = { 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 {