From 562c0dfca260c4dca1aecdd3a0edde924aafb8e8 Mon Sep 17 00:00:00 2001 From: Sai Nikhil Date: Mon, 15 Jun 2026 20:29:55 +0530 Subject: [PATCH] fix: add AI Observability checks (#12668) --- web/src/components/settings/index.vue | 2 +- web/src/composables/router.spec.ts | 4 - web/src/composables/router.ts | 8 +- web/src/composables/shared/useAIRoutes.ts | 82 ------------------- .../shared/useManagementRoutes.spec.ts | 23 ++++-- .../composables/shared/useManagementRoutes.ts | 30 +++---- web/src/enterprise/composables/router.ts | 57 +++++++++++-- web/src/layouts/MainLayout.vue | 5 +- 8 files changed, 87 insertions(+), 124 deletions(-) delete mode 100644 web/src/composables/shared/useAIRoutes.ts diff --git a/web/src/components/settings/index.vue b/web/src/components/settings/index.vue index 5ba587d2e6..4999d197ef 100644 --- a/web/src/components/settings/index.vue +++ b/web/src/components/settings/index.vue @@ -326,7 +326,7 @@ export default defineComponent({ description: "LLM providers for online evaluations", icon: "smart-toy", to: { name: "llmProviders", query: { org_identifier: org } }, - visible: !!z.online_evals_enabled, + visible: (isEnt || isCloud) && !!z.online_evals_enabled, dataTest: "llm-providers-tab", group: "Data & AI", }, diff --git a/web/src/composables/router.spec.ts b/web/src/composables/router.spec.ts index d9523dc56d..c701e44cc1 100644 --- a/web/src/composables/router.spec.ts +++ b/web/src/composables/router.spec.ts @@ -24,10 +24,6 @@ vi.mock("./shared/useIngestionRoutes", () => ({ default: vi.fn(() => mockIngestionRoutes), })); -vi.mock("./shared/useAIRoutes", () => ({ - default: vi.fn(() => ({ homeChildRoutes: [] })), -})); - import useOSRoutes from "./router"; import useIngestionRoutes from "./shared/useIngestionRoutes"; diff --git a/web/src/composables/router.ts b/web/src/composables/router.ts index 11f7287eee..1b1895208e 100644 --- a/web/src/composables/router.ts +++ b/web/src/composables/router.ts @@ -14,17 +14,11 @@ // along with this program. If not, see . import useIngestionRoutes from "./shared/useIngestionRoutes"; -import useAIRoutes from "./shared/useAIRoutes"; const useOSRoutes = () => { const parentRoutes: any = []; - const ai = useAIRoutes(); - - const homeChildRoutes: any[] = [ - ...useIngestionRoutes(), - ...ai.homeChildRoutes, - ]; + const homeChildRoutes: any[] = [...useIngestionRoutes()]; return { parentRoutes, homeChildRoutes }; }; diff --git a/web/src/composables/shared/useAIRoutes.ts b/web/src/composables/shared/useAIRoutes.ts deleted file mode 100644 index 91114e501e..0000000000 --- a/web/src/composables/shared/useAIRoutes.ts +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2026 OpenObserve Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -// AI Observability / Online Evals routes. Shared by both the open-source -// router (@/composables/router) and the enterprise router -// (@/enterprise/composables/router) so the `/ai` feature works in every build. -// Visibility of the sidebar entry is still gated on the `online_evals_enabled` -// zoConfig flag in MainLayout.vue — these routes only ensure the pages exist. - -import OnlineEvals from "@/enterprise/components/OnlineEvals.vue"; -import { routeGuard } from "@/utils/zincutils"; - -const AIObservabilityShell = () => - import("@/enterprise/views/AIObservability/Index.vue"); -const AILLMInsightsPage = () => - import("@/enterprise/views/AIObservability/LLMInsightsPage.vue"); -const AISessionsPage = () => - import("@/enterprise/views/AIObservability/SessionsPage.vue"); - -const useAIRoutes = () => { - // Mounted directly under the MainLayout (home) children. - const homeChildRoutes: any[] = [ - { - path: "ai", - component: AIObservabilityShell, - beforeEnter(to: any, from: any, next: any) { - routeGuard(to, from, next); - }, - meta: { - title: "AI Monitoring", - keepAlive: false, - }, - children: [ - { - path: "", - name: "aiObservability", - redirect: { name: "aiLLMInsights" }, - }, - { - path: "llm-insights", - name: "aiLLMInsights", - component: AILLMInsightsPage, - meta: { title: "LLM Insights", keepAlive: false }, - }, - { - path: "sessions", - name: "aiSessions", - component: AISessionsPage, - meta: { title: "Sessions", keepAlive: false }, - }, - { - path: "evaluations", - name: "aiEvaluations", - component: OnlineEvals, - props: { hideTabBar: true }, - meta: { title: "Evaluations", keepAlive: false }, - }, - ], - }, - { - // Legacy URL — keep saved/bookmarked links working - path: "online-evals", - redirect: { name: "aiEvaluations" }, - }, - ]; - - return { homeChildRoutes }; -}; - -export default useAIRoutes; diff --git a/web/src/composables/shared/useManagementRoutes.spec.ts b/web/src/composables/shared/useManagementRoutes.spec.ts index 946aa62e5d..f63769af85 100644 --- a/web/src/composables/shared/useManagementRoutes.spec.ts +++ b/web/src/composables/shared/useManagementRoutes.spec.ts @@ -145,6 +145,11 @@ describe("useManagementRoutes", () => { expect(templateRoute.path).toBe("templates"); }); + it("should NOT have llmProviders route in OSS builds", () => { + const llmRoute = routes[0].children.find((child: any) => child.name === "llmProviders"); + expect(llmRoute).toBeUndefined(); + }); + it("should have beforeEnter hook for general route", () => { const generalRoute = routes[0].children.find((child: any) => child.name === "general"); expect(typeof generalRoute.beforeEnter).toBe("function"); @@ -502,63 +507,63 @@ describe("useManagementRoutes", () => { config.isEnterprise = "false"; config.isCloud = "false"; const routes = useManagementRoutes(); - expect(routes[0].children).toHaveLength(7); // Only base routes + expect(routes[0].children).toHaveLength(6); // Only base routes (llm_providers is enterprise/cloud-only) }); it("should handle isCloud as string 'false'", () => { config.isEnterprise = "false"; config.isCloud = "false"; const routes = useManagementRoutes(); - expect(routes[0].children).toHaveLength(7); // Only base routes + expect(routes[0].children).toHaveLength(6); // Only base routes (llm_providers is enterprise/cloud-only) }); it("should handle isEnterprise as undefined", () => { (config as any).isEnterprise = undefined; config.isCloud = "false"; const routes = useManagementRoutes(); - expect(routes[0].children).toHaveLength(7); // Only base routes + expect(routes[0].children).toHaveLength(6); // Only base routes (llm_providers is enterprise/cloud-only) }); it("should handle isCloud as undefined", () => { config.isEnterprise = "false"; (config as any).isCloud = undefined; const routes = useManagementRoutes(); - expect(routes[0].children).toHaveLength(7); // Only base routes + expect(routes[0].children).toHaveLength(6); // Only base routes (llm_providers is enterprise/cloud-only) }); it("should handle both config values as undefined", () => { (config as any).isEnterprise = undefined; (config as any).isCloud = undefined; const routes = useManagementRoutes(); - expect(routes[0].children).toHaveLength(7); // Only base routes + expect(routes[0].children).toHaveLength(6); // Only base routes (llm_providers is enterprise/cloud-only) }); it("should handle isEnterprise as non-string truthy value", () => { (config as any).isEnterprise = true; config.isCloud = "false"; const routes = useManagementRoutes(); - expect(routes[0].children).toHaveLength(7); // Only base routes, since config comparison is strict "true" + expect(routes[0].children).toHaveLength(6); // Only base routes (llm_providers is enterprise/cloud-only), since config comparison is strict "true" }); it("should handle isCloud as non-string truthy value", () => { config.isEnterprise = "false"; (config as any).isCloud = true; const routes = useManagementRoutes(); - expect(routes[0].children).toHaveLength(7); // Only base routes, since config comparison is strict "true" + expect(routes[0].children).toHaveLength(6); // Only base routes (llm_providers is enterprise/cloud-only), since config comparison is strict "true" }); it("should handle empty string for isEnterprise", () => { config.isEnterprise = ""; config.isCloud = "false"; const routes = useManagementRoutes(); - expect(routes[0].children).toHaveLength(7); // Only base routes + expect(routes[0].children).toHaveLength(6); // Only base routes (llm_providers is enterprise/cloud-only) }); it("should handle empty string for isCloud", () => { config.isEnterprise = "false"; config.isCloud = ""; const routes = useManagementRoutes(); - expect(routes[0].children).toHaveLength(7); // Only base routes + expect(routes[0].children).toHaveLength(6); // Only base routes (llm_providers is enterprise/cloud-only) }); it("should return the same structure on multiple calls", () => { diff --git a/web/src/composables/shared/useManagementRoutes.ts b/web/src/composables/shared/useManagementRoutes.ts index 0d15148416..7aaf0a193c 100644 --- a/web/src/composables/shared/useManagementRoutes.ts +++ b/web/src/composables/shared/useManagementRoutes.ts @@ -89,23 +89,25 @@ const useManagementRoutes = () => { routeGuard(to, from, next); }, }, - { - // Available in all builds (used by the AI Observability / Online - // Evals flows), not just enterprise. - path: "llm_providers", - name: "llmProviders", - component: () => - import("@/components/settings/LlmProvidersSettings.vue"), - meta: { - title: "LLM Providers", - }, - beforeEnter(to: any, from: any, next: any) { - routeGuard(to, from, next); - }, - }, ], }, ]; + // LLM Providers (used by the AI Observability / Online Evals flows) is an + // enterprise/cloud-only feature — not available in OSS builds. + if (config.isEnterprise == "true" || config.isCloud == "true") { + routes[0].children.push({ + path: "llm_providers", + name: "llmProviders", + component: () => + import("@/components/settings/LlmProvidersSettings.vue"), + meta: { + title: "LLM Providers", + }, + beforeEnter(to: any, from: any, next: any) { + routeGuard(to, from, next); + }, + }); + } if (config.isEnterprise == "true") { routes[0].children.push( ...[ diff --git a/web/src/enterprise/composables/router.ts b/web/src/enterprise/composables/router.ts index d83fa899fe..0fd663f637 100644 --- a/web/src/enterprise/composables/router.ts +++ b/web/src/enterprise/composables/router.ts @@ -20,13 +20,17 @@ import Usage from "@/enterprise/components/billings/usage.vue"; import BillingGroup from "@/enterprise/components/billings/BillingGroup.vue"; import AzureMarketplaceSetup from "@/views/AzureMarketplaceSetup.vue"; import AwsMarketplaceSetup from "@/views/AwsMarketplaceSetup.vue"; -import useAIRoutes from "@/composables/shared/useAIRoutes"; +import OnlineEvals from "@/enterprise/components/OnlineEvals.vue"; +import { routeGuard } from "@/utils/zincutils"; + +const AIObservabilityShell = () => + import("@/enterprise/views/AIObservability/Index.vue"); +const AILLMInsightsPage = () => + import("@/enterprise/views/AIObservability/LLMInsightsPage.vue"); +const AISessionsPage = () => + import("@/enterprise/views/AIObservability/SessionsPage.vue"); const useEnvRoutes = () => { - // AI Observability / Online Evals routes are shared with the open-source - // router so the feature is available in every build. - const ai = useAIRoutes(); - // Note: AWS Marketplace registration is handled by backend at POST /api/aws-marketplace/register // The backend sets a cookie and redirects to Dex login const parentRoutes: any = [ @@ -57,7 +61,48 @@ const useEnvRoutes = () => { ]; const homeChildRoutes = [ - ...ai.homeChildRoutes, + { + path: "ai", + component: AIObservabilityShell, + beforeEnter(to: any, from: any, next: any) { + routeGuard(to, from, next); + }, + meta: { + title: "AI Monitoring", + keepAlive: false, + }, + children: [ + { + path: "", + name: "aiObservability", + redirect: { name: "aiLLMInsights" }, + }, + { + path: "llm-insights", + name: "aiLLMInsights", + component: AILLMInsightsPage, + meta: { title: "LLM Insights", keepAlive: false }, + }, + { + path: "sessions", + name: "aiSessions", + component: AISessionsPage, + meta: { title: "Sessions", keepAlive: false }, + }, + { + path: "evaluations", + name: "aiEvaluations", + component: OnlineEvals, + props: { hideTabBar: true }, + meta: { title: "Evaluations", keepAlive: false }, + }, + ], + }, + { + // Legacy URL — keep saved/bookmarked links working + path: "online-evals", + redirect: { name: "aiEvaluations" }, + }, { path: "billings", name: "billings", diff --git a/web/src/layouts/MainLayout.vue b/web/src/layouts/MainLayout.vue index d80ca3eaad..a46a4864fc 100644 --- a/web/src/layouts/MainLayout.vue +++ b/web/src/layouts/MainLayout.vue @@ -333,7 +333,10 @@ export default defineComponent({ // of whether the config response arrived before or after this component // mounted. const isOnlineEvalsEnabled = computed(() => { - return Boolean(store.state.zoConfig?.online_evals_enabled); + return ( + (config.isEnterprise == "true" || config.isCloud == "true") && + Boolean(store.state.zoConfig?.online_evals_enabled) + ); }); const orgOptions = ref([{ label: Number, value: String }]);