Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
36efb5a
Add Comet Debugger Mode feature and related components
JetoPistola Oct 28, 2025
0296fa0
Add Opik icon to AppDebugInfo component
JetoPistola Nov 7, 2025
2aad3c7
Enhance Switch Component with Extra Small Size Variant
JetoPistola Nov 7, 2025
0572251
Enhance AppNetworkStatus component with styled RTT display
JetoPistola Nov 7, 2025
d52576e
Add cometDebuggerModeEnabled toggle to ServiceTogglesConfig
JetoPistola Nov 7, 2025
3540363
Update AppDebugInfo component to conditionally render OPIK version di…
JetoPistola Nov 7, 2025
aa1d272
Update end-to-end workflows and configuration for Comet Debugger Mode
JetoPistola Nov 7, 2025
2a9ce72
[OPIK-2848] Fix Tailwind CSS classnames order
JetoPistola Nov 7, 2025
4325754
Refactor UserMenu component to use logical AND for APP_VERSION check
JetoPistola Nov 7, 2025
576b15d
Wrap CometIcon in a span within AppNetworkStatus component for improv…
JetoPistola Nov 7, 2025
4dbd0e5
Revision 2: Add keyboard shortcut for debugger mode and remove menu t…
JetoPistola Nov 9, 2025
51f169b
Revision 2: Remove COMET_DEBUGGER_MODE from feature toggles
JetoPistola Nov 9, 2025
ba43032
Revision 3: Revert to ThemeToggle and remove redundant empty line
JetoPistola Nov 9, 2025
d7f810e
Revision 4: Use absolute imports for ThemeToggle component
JetoPistola Nov 9, 2025
e2842f6
Revision 5: Remove redundant DebugStore and use simple localStorage s…
JetoPistola Nov 9, 2025
c9ce853
Revision 2: Refactor AppDebugInfo to use react-hotkeys-hook and make …
JetoPistola Nov 10, 2025
2bb63cb
Revision 3: Move AppNetworkStatus to layout components
JetoPistola Nov 10, 2025
8c1312e
Revision 4: Move AppDebugInfo to layout components
JetoPistola Nov 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/end2end_suites.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:
OPIK_USAGE_REPORT_ENABLED: false
run: |
cd ${{ github.workspace }}
TOGGLE_WELCOME_WIZARD_ENABLED="false" ./opik.sh --build
TOGGLE_WELCOME_WIZARD_ENABLED="false" TOGGLE_COMET_DEBUGGER_MODE_ENABLED="false" ./opik.sh --build

- name: Check Docker pods are up (Local)
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/end2end_suites_typescript.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ jobs:
OPIK_USAGE_REPORT_ENABLED: false
run: |
cd ${{ github.workspace }}
TOGGLE_WELCOME_WIZARD_ENABLED="false" ./opik.sh --build
TOGGLE_WELCOME_WIZARD_ENABLED="false" TOGGLE_COMET_DEBUGGER_MODE_ENABLED="false" ./opik.sh --build

- name: Check Docker pods are up (Local)
run: |
Expand Down
4 changes: 4 additions & 0 deletions apps/opik-backend/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,10 @@ serviceToggles:
# Default: false
# Description: Whether or not Welcome Wizard is enabled
welcomeWizardEnabled: ${TOGGLE_WELCOME_WIZARD_ENABLED:-"false"}
# Default: true
# Description: Whether or not Comet Debugger Mode is enabled
cometDebuggerModeEnabled: ${TOGGLE_COMET_DEBUGGER_MODE_ENABLED:-"true"}


# Trace Thread configuration
traceThreadConfig:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ public class ServiceTogglesConfig {
@NotNull boolean alertsEnabled;
@JsonProperty
@NotNull boolean welcomeWizardEnabled;
@JsonProperty
@NotNull boolean cometDebuggerModeEnabled;
}
42 changes: 42 additions & 0 deletions apps/opik-frontend/src/api/debug/useIsAlive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import api from "@/api/api";
import { useQuery } from "@tanstack/react-query";

const PING_FETCHING_TIMEOUT_SECONDS = 5;
const CONNECTED_PING_REFETCH_INTERVAL_SECONDS = 10;
const DISCONNECTED_PING_REFETCH_INTERVAL_SECONDS = 5;

interface IsAlivePingResponse {
healthy: boolean;
rtt: number;
}

const getPing = async (): Promise<IsAlivePingResponse> => {
const startTime = performance.now();

const { data } = await api.get<IsAlivePingResponse>("/is-alive/ping", {
timeout: PING_FETCHING_TIMEOUT_SECONDS * 1000,
});

const endTime = performance.now();

const rtt = endTime - startTime;

return { ...data, rtt };
};

export const usePingBackend = (isNetworkOnline: boolean) =>
useQuery({
queryKey: ["backend-ping"],
queryFn: getPing,
enabled: isNetworkOnline,
retryDelay: 1000,
refetchInterval: (query) => {
const { error: isError, data } = query.state;
const isConnected = !isError && data?.healthy;

return isConnected
? CONNECTED_PING_REFETCH_INTERVAL_SECONDS * 1000
: DISCONNECTED_PING_REFETCH_INTERVAL_SECONDS * 1000;
},
refetchOnReconnect: true,
});
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const DEFAULT_STATE: FeatureToggles = {
[FeatureToggleKeys.TOGGLE_OPIK_AI_ENABLED]: false,
[FeatureToggleKeys.TOGGLE_ALERTS_ENABLED]: false,
[FeatureToggleKeys.WELCOME_WIZARD_ENABLED]: false,
[FeatureToggleKeys.COMET_DEBUGGER_MODE_ENABLED]: false,
};

const initialState: FeatureTogglesState = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useIsFeatureEnabled } from "@/components/feature-toggles-provider";
import { toast } from "@/components/ui/use-toast";
import { APP_VERSION } from "@/constants/app";
import AppNetworkStatus from "@/plugins/comet/AppNetworkStatus";
import OpikIcon from "@/icons/opik.svg?react";
import { COMET_DEBUGGER_MODE_KEY } from "@/plugins/comet/UserMenuAppDebugInfoToggle";
import { useDebugStore } from "@/store/DebugStore";
import { FeatureToggleKeys } from "@/types/feature-toggles";
import copy from "clipboard-copy";
import { Copy } from "lucide-react";
import { useEffect } from "react";

const AppDebugInfo = () => {
const showAppDebugInfo = useDebugStore((state) => state.showAppDebugInfo);
const isCometDebuggerModeEnabled = useIsFeatureEnabled(
FeatureToggleKeys.COMET_DEBUGGER_MODE_ENABLED,
);
const setShowAppDebugInfo = useDebugStore(
(state) => state.setShowAppDebugInfo,
);

useEffect(() => {
const localStorageValue = localStorage.getItem(COMET_DEBUGGER_MODE_KEY);
const shouldShowAppDebugInfo =
isCometDebuggerModeEnabled && localStorageValue?.toLowerCase() === "true";

setShowAppDebugInfo(shouldShowAppDebugInfo);
}, [isCometDebuggerModeEnabled, setShowAppDebugInfo]);

return (
showAppDebugInfo && (
<>
<div className="flex items-center">
<AppNetworkStatus />
</div>

{APP_VERSION && (
<div
className="flex items-center gap-2"
onClick={() => {
copy(APP_VERSION);
toast({ description: "Successfully copied version" });
}}
>
<span className="comet-body-s-accented flex items-center gap-2 truncate">
<OpikIcon className="size-5" />
OPIK VERSION {APP_VERSION}
</span>
<Copy className="size-4 shrink-0" />
</div>
)}
</>
)
);
};

export default AppDebugInfo;
92 changes: 92 additions & 0 deletions apps/opik-frontend/src/components/layout/NoUserMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Check, Copy, Settings2 } from "lucide-react";
import copy from "clipboard-copy";

import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuPortal,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useThemeOptions } from "@/hooks/useThemeOptions";
import { APP_VERSION } from "@/constants/app";
import { toast } from "../ui/use-toast";
import UserMenuAppDebugInfoToggle from "@/plugins/comet/UserMenuAppDebugInfoToggle";
import { FeatureToggleKeys } from "@/types/feature-toggles";
import { useIsFeatureEnabled } from "../feature-toggles-provider";

const NoUserMenu = () => {
const isCometDebuggerModeEnabled = useIsFeatureEnabled(
FeatureToggleKeys.COMET_DEBUGGER_MODE_ENABLED,
);
const { theme, themeOptions, currentOption, CurrentIcon, handleThemeSelect } =
useThemeOptions();

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon-sm">
<Settings2 className="size-[1.2rem]" />
<span className="sr-only">
Current theme: {currentOption?.label || "Unknown"}
</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuGroup>
{isCometDebuggerModeEnabled && <UserMenuAppDebugInfoToggle />}
<DropdownMenuSub>
<DropdownMenuSubTrigger className="flex cursor-pointer items-center">
<CurrentIcon className="mr-2 size-4" />
<span>Theme</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
{themeOptions.map(({ value, label, icon: Icon }) => (
<DropdownMenuItem
key={value}
className="cursor-pointer"
onClick={() => handleThemeSelect(value)}
>
<div className="relative flex w-full items-center pl-6">
{theme === value && (
<Check className="absolute left-0 size-4" />
)}
<Icon className="mr-2 size-4" />
<span>{label}</span>
</div>
</DropdownMenuItem>
))}
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
</DropdownMenuGroup>
{APP_VERSION && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem
className="cursor-pointer justify-center text-light-slate"
onClick={() => {
copy(APP_VERSION);
toast({ description: "Successfully copied version" });
}}
>
<span className="comet-body-xs-accented truncate ">
VERSION {APP_VERSION}
</span>
<Copy className="ml-2 size-3 shrink-0" />
</DropdownMenuItem>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
);
};

export default NoUserMenu;
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import useAppStore from "@/store/AppStore";
import usePluginsStore from "@/store/PluginsStore";
import { Link, Outlet } from "@tanstack/react-router";
import Logo from "@/components/layout/Logo/Logo";
import ThemeToggle from "@/components/layout/ThemeToggle/ThemeToggle";
import AppDebugInfo from "../AppDebugInfo/AppDebugInfo";
import NoUserMenu from "../NoUserMenu";

export const PartialPageLayout = ({
children = <Outlet />,
Expand All @@ -30,7 +31,8 @@ export const PartialPageLayout = ({
</Link>
</div>

{UserMenu ? <UserMenu /> : <ThemeToggle />}
<AppDebugInfo />
{UserMenu ? <UserMenu /> : <NoUserMenu />}
</nav>

<section className="comet-header-inset absolute inset-x-0 bottom-0 overflow-auto bg-soft-background px-6">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";
import usePluginsStore from "@/store/PluginsStore";
import { Outlet } from "@tanstack/react-router";
import Logo from "@/components/layout/Logo/Logo";
import ThemeToggle from "@/components/layout/ThemeToggle/ThemeToggle";
import NoUserMenu from "../NoUserMenu";

export const SMEPageLayout = ({
children = <Outlet />,
Expand All @@ -22,7 +22,7 @@ export const SMEPageLayout = ({
<main>
<nav className="comet-header-height flex w-full items-center justify-between gap-6 border-b pl-4 pr-6">
<div className="flex-1 pl-0.5">{logo}</div>
<ThemeToggle />
<NoUserMenu />
</nav>

<section className="comet-header-inset absolute inset-x-0 bottom-0 overflow-auto bg-soft-background px-6">
Expand Down
7 changes: 4 additions & 3 deletions apps/opik-frontend/src/components/layout/TopBar/TopBar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import Breadcrumbs from "@/components/layout/Breadcrumbs/Breadcrumbs";
import usePluginsStore from "@/store/PluginsStore";
import ThemeToggle from "../ThemeToggle/ThemeToggle";
import AppDebugInfo from "../AppDebugInfo/AppDebugInfo";
import NoUserMenu from "../NoUserMenu";

const TopBar = () => {
const UserMenu = usePluginsStore((state) => state.UserMenu);
Expand All @@ -12,7 +12,8 @@ const TopBar = () => {
<Breadcrumbs />
</div>

{UserMenu ? <UserMenu /> : <ThemeToggle />}
<AppDebugInfo />
{UserMenu ? <UserMenu /> : <NoUserMenu />}
</nav>
);
};
Expand Down
2 changes: 2 additions & 0 deletions apps/opik-frontend/src/components/ui/switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const switchVariants = cva(
size: {
default: "h-6 w-11",
sm: "h-5 w-9",
xs: "h-4 w-7",
},
},
defaultVariants: {
Expand All @@ -27,6 +28,7 @@ const switchThumbVariants = cva(
default:
"size-5 data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0",
sm: "size-4 data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0",
xs: "size-3 data-[state=checked]:translate-x-3 data-[state=unchecked]:translate-x-0",
},
},
defaultVariants: {
Expand Down
21 changes: 21 additions & 0 deletions apps/opik-frontend/src/hooks/useIsNetworkOnline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useState, useEffect } from "react";

const useIsNetworkOnline = () => {
const [isNetworkOnline, setIsNetworkOnline] = useState(navigator.onLine);

useEffect(() => {
const updateNetworkStatus = () => setIsNetworkOnline(navigator.onLine);

window.addEventListener("online", updateNetworkStatus);
window.addEventListener("offline", updateNetworkStatus);

return () => {
window.removeEventListener("online", updateNetworkStatus);
window.removeEventListener("offline", updateNetworkStatus);
};
}, []);

return isNetworkOnline;
};

export default useIsNetworkOnline;
1 change: 1 addition & 0 deletions apps/opik-frontend/src/icons/comet.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions apps/opik-frontend/src/icons/opik.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading