Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion app-next/src/components/dataset/data-detail-section.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import { Database, FileText, Download, ExternalLink } from "lucide-react";
import { APP_CONFIG } from "@/lib/config";
import { entityTailwindColors } from "@/constants/entityColors";
import { Button } from "@/components/ui/button";
import { CollapsibleSection } from "@/components/ui/collapsible-section";
Expand All @@ -21,7 +22,7 @@ interface DataDetailSectionProps {
* - Data preview link
*/
export function DataDetailSection({ dataset }: DataDetailSectionProps) {
const apiUrl = process.env.NEXT_PUBLIC_OPENML_URL || "https://www.openml.org";
const apiUrl = APP_CONFIG.openmlApiUrl || "https://www.openml.org";

return (
<CollapsibleSection
Expand Down
9 changes: 9 additions & 0 deletions app-next/src/components/dataset/runs-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ function getInitials(name: string): string {
export function RunsSection({ dataset, runCount }: RunsSectionProps) {
const [runs, setRuns] = useState<Run[]>([]);
const [loading, setLoading] = useState(false);
const [notAvailable, setNotAvailable] = useState(false);

useEffect(() => {
async function loadRuns() {
Expand All @@ -69,6 +70,10 @@ export function RunsSection({ dataset, runCount }: RunsSectionProps) {
`/api/dataset/${dataset.data_id}/runs?limit=10`,
);
if (!response.ok) {
if (response.status === 404) {
setNotAvailable(true);
return;
}
throw new Error("Failed to fetch runs");
}
const data = await response.json();
Expand Down Expand Up @@ -186,6 +191,10 @@ export function RunsSection({ dataset, runCount }: RunsSectionProps) {
))}
</div>
</>
) : notAvailable ? (
<div className="text-muted-foreground py-8 text-center text-sm">
Runs are not available for this dataset on the current server.
</div>
) : (
<div className="text-muted-foreground py-8 text-center text-sm">
No runs found for this dataset
Expand Down
9 changes: 9 additions & 0 deletions app-next/src/components/dataset/tasks-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ interface Task {
export function TasksSection({ dataset, taskCount }: TasksSectionProps) {
const [tasks, setTasks] = useState<Task[]>([]);
const [loading, setLoading] = useState(false);
const [notAvailable, setNotAvailable] = useState(false);

useEffect(() => {
async function loadTasks() {
Expand All @@ -56,6 +57,10 @@ export function TasksSection({ dataset, taskCount }: TasksSectionProps) {
`/api/dataset/${dataset.data_id}/tasks?limit=10`,
);
if (!response.ok) {
if (response.status === 404) {
setNotAvailable(true);
return;
}
throw new Error("Failed to fetch tasks");
}
const data = await response.json();
Expand Down Expand Up @@ -152,6 +157,10 @@ export function TasksSection({ dataset, taskCount }: TasksSectionProps) {
))}
</div>
</>
) : notAvailable ? (
<div className="text-muted-foreground py-8 text-center text-sm">
Tasks are not available for this dataset on the current server.
</div>
) : (
<div className="text-muted-foreground py-8 text-center text-sm">
No tasks found for this dataset
Expand Down
52 changes: 30 additions & 22 deletions app-next/src/components/header/search-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,60 +58,68 @@ export function SearchBar() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [urlQuery]);

// When on a search sub-tab (e.g. /datasets/runs), preserve that sub-path so
// searching stays on the same tab. Detail pages (e.g. /datasets/47155) always
// fall back to the root search route.
const getRoute = useCallback(
(indexKey: string): string => {
const index = searchIndices.find((i) => i.key === indexKey);
if (!index) return "/";
if (!effectivePath.startsWith(index.route)) return index.route;
// Only preserve sub-paths that look like named tabs (no numeric/slug IDs).
// e.g. /datasets/runs ✅ — /datasets/47155 ❌ — /measures/wall-clock-time ❌
const subPath = effectivePath.slice(index.route.length);
const nextSegment = subPath.split("/").filter(Boolean)[0] ?? "";
const isDetailPage = nextSegment !== "" && !/^[a-z]+$/.test(nextSegment);
return isDetailPage ? index.route : effectivePath;
},
[effectivePath],
);

// Debounced navigation - centralized and predictable
const debouncedNavigate = useDebouncedCallback(
(value: string, route: string) => {
if (!value.trim()) return;
// Use replace to avoid polluting history while typing
router.replace(`${route}?q=${encodeURIComponent(value)}`);
},
300,
700,
);

// Handle input change - update local state immediately, navigate after debounce
const handleInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value;
setInputValue(newValue);

// Get current route for navigation
const currentIndex = searchIndices.find((i) => i.key === selectedIndex);
if (currentIndex && newValue.trim()) {
debouncedNavigate(newValue, currentIndex.route);
if (newValue.trim()) {
debouncedNavigate(newValue, getRoute(selectedIndex));
}
},
[selectedIndex, debouncedNavigate],
[selectedIndex, getRoute, debouncedNavigate],
);

// Handle form submit (Enter key) - navigate immediately
const handleSearch = useCallback(
(e: React.FormEvent) => {
e.preventDefault();
if (inputValue.trim()) {
const currentIndex = searchIndices.find((i) => i.key === selectedIndex);
if (currentIndex) {
router.push(
`${currentIndex.route}?q=${encodeURIComponent(inputValue)}`,
);
}
router.push(`${getRoute(selectedIndex)}?q=${encodeURIComponent(inputValue)}`);
}
},
[inputValue, selectedIndex, router],
[inputValue, selectedIndex, getRoute, router],
);

// Handle index (entity type) change
const handleIndexChange = useCallback(
(value: string) => {
const newIndex = searchIndices.find((i) => i.key === value);
if (newIndex) {
if (inputValue.trim()) {
router.push(`${newIndex.route}?q=${encodeURIComponent(inputValue)}`);
} else {
router.push(newIndex.route);
}
const route = getRoute(value);
if (inputValue.trim()) {
router.push(`${route}?q=${encodeURIComponent(inputValue)}`);
} else {
router.push(route);
}
},
[inputValue, router],
[inputValue, getRoute, router],
);

return (
Expand Down
80 changes: 39 additions & 41 deletions app-next/src/components/layout/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from "lucide-react";
import { useSession, signOut } from "next-auth/react";
import { useState, useEffect } from "react";
import { useQuery } from "@tanstack/react-query";
import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
import { navItems, type NavItem } from "@/constants";
Expand All @@ -42,7 +43,6 @@ export function Sidebar() {
const isHomePage = pathname === "/";
const { isCollapsed, setIsCollapsed, homeMenuOpen, setHomeMenuOpen } =
useSidebar();
const [counts, setCounts] = useState<Record<string, number>>({});
const { data: session, status } = useSession();
const [user, setUser] = useState<{
name: string;
Expand All @@ -61,7 +61,7 @@ export function Sidebar() {
const name =
session.user.name ||
`${firstName} ${lastName}`.trim() ||
(session.user as any).username ||
session.user.username ||
session.user.email?.split("@")[0] ||
"User";
const email = session.user.email || "";
Expand All @@ -88,51 +88,48 @@ export function Sidebar() {
}
}, [status, session]);

// Fetch entity counts on mount
useEffect(() => {
fetch("/api/count")
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
})
.then((data) => {
if (Array.isArray(data)) {
const countsMap = data.reduce(
(
acc: Record<string, number>,
item: { index: string; count: number },
) => {
acc[item.index] = item.count;
return acc;
},
{},
);
setCounts(countsMap);
} else {
console.warn(
"⚠️ API returned non-array data, counts unavailable:",
data,
);
}
})
.catch((error) => {
// Fetch entity counts with React Query (deduplicates requests, caches results)
const { data: counts = {} } = useQuery<Record<string, number>>({
queryKey: ["entity-counts"],
queryFn: async () => {
const response = await fetch("/api/count");
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (!Array.isArray(data)) {
console.warn(
"⚠️ Could not fetch entity counts (sidebar will work without them):",
error.message,
"⚠️ API returned non-array data, counts unavailable:",
data,
);
});
}, []);
return {};
}
return data.reduce(
(
acc: Record<string, number>,
item: { index: string; count: number },
) => {
acc[item.index] = item.count;
return acc;
},
{},
);
},
staleTime: 5 * 60 * 1000, // Cache counts for 5 minutes
retry: 1,
});

// Render mobile/tablet unified menu (< 1024px for all pages, all sizes for homepage)
const renderUnifiedMenu = () => (
<>
{/* Overlay when menu is open */}
{homeMenuOpen && (
<div
className="fixed inset-0 z-40 bg-slate-700/20"
className="fixed inset-0 z-40 bg-white/10 dark:bg-slate-950/20"
onClick={() => setHomeMenuOpen(false)}
style={{
filter: "contrast(1.75)",
}}
/>
)}

Expand All @@ -151,17 +148,17 @@ export function Sidebar() {
>
{/* Logo Header - Only show when menu is open */}
{homeMenuOpen && (
<div className="relative flex min-h-40 shrink-0 items-start justify-center bg-[#233044] py-6">
<div className="relative flex shrink-0 items-center justify-center bg-[#233044] py-4">
<Link
href="/"
className="group flex w-64 items-start justify-center"
className="group flex w-64 items-center justify-center"
onClick={() => setHomeMenuOpen(false)}
>
<Image
src="/logo_openML_dark-bkg.png"
alt="OpenML Logo"
width={100}
height={50}
width={140}
height={70}
className="h-auto w-auto object-contain transition-transform duration-300 ease-out group-hover:scale-110"
style={{
animation: "logoFadeScale 0.4s ease-out 0.2s both",
Expand Down Expand Up @@ -531,6 +528,7 @@ function SidebarItem({
isActive && "bg-[#1E2A38] font-medium text-white",
)}
>
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
<Link
href={item.href as any}
className="flex items-center justify-between"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ export function createBenchmarkConfig(studyType: "task" | "run") {
flows_included: { raw: {} },
runs_included: { raw: {} },
},
disjunctiveFacets: ["uploader.keyword"],
disjunctiveFacets: ["uploader.keyword", "visibility"],
facets: {
"uploader.keyword": { type: "value", size: 20 },
"visibility": { type: "value", size: 5 },
},
},
initialState: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import Link from "next/link";
import { WithSearch, Paging } from "@elastic/react-search-ui";
import { FilterBar } from "../shared/filter-bar";
import { ControlsBar } from "../shared/controls-bar";
import { Badge } from "@/components/ui/badge";
import { TrendingUp, Calendar, User, Target } from "lucide-react";

interface BenchmarkResult {
Expand Down
Loading