Skip to content
Merged
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
37 changes: 33 additions & 4 deletions sdk/python/feast/ui_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,45 @@ def shutdown_event():
with importlib_resources.as_file(ui_dir_ref) as ui_dir:
# Initialize with the projects-list.json file
with ui_dir.joinpath("projects-list.json").open(mode="w") as f:
projects_dict = {
"projects": [
# Get all projects from the registry
discovered_projects = []
registry = store.registry.proto()

# Use the projects list from the registry
if registry and registry.projects and len(registry.projects) > 0:
for proj in registry.projects:
if proj.spec and proj.spec.name:
discovered_projects.append(
{
"name": proj.spec.name.replace("_", " ").title(),
"description": proj.spec.description
or f"Project: {proj.spec.name}",
"id": proj.spec.name,
"registryPath": f"{root_path}/registry",
}
)
else:
# If no projects in registry, use the current project from feature_store.yaml
discovered_projects.append(
{
"name": "Project",
"description": "Test project",
"id": project_id,
"registryPath": f"{root_path}/registry",
}
]
}
)

# Add "All Projects" option at the beginning if there are multiple projects
if len(discovered_projects) > 1:
all_projects_entry = {
"name": "All Projects",
"description": "View data across all projects",
"id": "all",
"registryPath": f"{root_path}/registry",
}
discovered_projects.insert(0, all_projects_entry)

projects_dict = {"projects": discovered_projects}
f.write(json.dumps(projects_dict))

@app.get("/registry")
Expand Down
25 changes: 12 additions & 13 deletions ui/src/components/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ const CommandPalette: React.FC<CommandPaletteProps> = ({
? String(item.spec.description || "")
: "",
type: getItemType(item, name),
projectId: "projectId" in item ? String(item.projectId) : undefined,
};
});

Expand All @@ -158,15 +159,7 @@ const CommandPalette: React.FC<CommandPaletteProps> = ({
};
});

console.log(
"CommandPalette isOpen:",
isOpen,
"categories:",
categories.length,
); // Debug log

if (!isOpen) {
console.log("CommandPalette not rendering due to isOpen=false");
return null;
}

Expand Down Expand Up @@ -227,16 +220,11 @@ const CommandPalette: React.FC<CommandPaletteProps> = ({
href={item.link}
onClick={(e) => {
e.preventDefault();
console.log(
"Search result clicked:",
item.name,
);

onClose();

setSearchText("");

console.log("Navigating to:", item.link);
navigate(item.link);
}}
style={{
Expand All @@ -253,6 +241,17 @@ const CommandPalette: React.FC<CommandPaletteProps> = ({
{item.description}
</div>
)}
{item.projectId && (
<div
style={{
fontSize: "0.85em",
color: "#69707D",
marginTop: "4px",
}}
>
Project: {item.projectId}
</div>
)}
</EuiFlexItem>
{item.type && (
<EuiFlexItem grow={false}>
Expand Down
10 changes: 0 additions & 10 deletions ui/src/components/GlobalSearchShortcut.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,13 @@ const GlobalSearchShortcut: React.FC<GlobalSearchShortcutProps> = ({
}) => {
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
console.log(
"Key pressed:",
event.key,
"metaKey:",
event.metaKey,
"ctrlKey:",
event.ctrlKey,
);
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
console.log("Cmd+K detected, preventing default and calling onOpen");
event.preventDefault();
event.stopPropagation();
onOpen();
}
};

console.log("Adding keydown event listener to window");
window.addEventListener("keydown", handleKeyDown, true);
return () => {
window.removeEventListener("keydown", handleKeyDown, true);
Expand Down
18 changes: 16 additions & 2 deletions ui/src/components/ProjectSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { EuiSelect, useGeneratedHtmlId } from "@elastic/eui";
import React from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useNavigate, useParams, useLocation } from "react-router-dom";
import { useLoadProjectsList } from "../contexts/ProjectListContext";

const ProjectSelector = () => {
const { projectName } = useParams();
const navigate = useNavigate();
const location = useLocation();

const { isLoading, data } = useLoadProjectsList();

Expand All @@ -22,7 +23,20 @@ const ProjectSelector = () => {

const basicSelectId = useGeneratedHtmlId({ prefix: "basicSelect" });
const onChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
navigate(`/p/${e.target.value}`);
const newProjectId = e.target.value;

// If we're on a project page, maintain the current path context
if (projectName && location.pathname.startsWith(`/p/${projectName}`)) {
// Replace the old project name with the new one in the current path
const newPath = location.pathname.replace(
`/p/${projectName}`,
`/p/${newProjectId}`,
);
navigate(newPath);
} else {
// Otherwise, just navigate to the project home
navigate(`/p/${newProjectId}`);
}
};

return (
Expand Down
12 changes: 12 additions & 0 deletions ui/src/components/RegistrySearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ const RegistrySearch = forwardRef<RegistrySearchRef, RegistrySearchProps>(
? String(item.spec.description || "")
: "",
type: getItemType(item, name),
projectId: "projectId" in item ? String(item.projectId) : undefined,
};
});

Expand Down Expand Up @@ -187,6 +188,17 @@ const RegistrySearch = forwardRef<RegistrySearchRef, RegistrySearchProps>(
{item.description}
</div>
)}
{item.projectId && (
<div
style={{
fontSize: "0.85em",
color: "#69707D",
marginTop: "4px",
}}
>
Project: {item.projectId}
</div>
)}
</EuiFlexItem>
{item.type && (
<EuiFlexItem grow={false}>
Expand Down
7 changes: 6 additions & 1 deletion ui/src/components/RegistryVisualizationTab.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useContext, useState } from "react";
import { useParams } from "react-router-dom";
import {
EuiEmptyPrompt,
EuiLoadingSpinner,
Expand All @@ -16,7 +17,11 @@ import { filterPermissionsByAction } from "../utils/permissionUtils";

const RegistryVisualizationTab = () => {
const registryUrl = useContext(RegistryPathContext);
const { isLoading, isSuccess, isError, data } = useLoadRegistry(registryUrl);
const { projectName } = useParams();
const { isLoading, isSuccess, isError, data } = useLoadRegistry(
registryUrl,
projectName,
);
const [selectedObjectType, setSelectedObjectType] = useState("");
const [selectedObjectName, setSelectedObjectName] = useState("");
const [selectedPermissionAction, setSelectedPermissionAction] = useState("");
Expand Down
4 changes: 2 additions & 2 deletions ui/src/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ const registry = readFileSync(

const projectsListWithDefaultProject = http.get("/projects-list.json", () =>
HttpResponse.json({
default: "credit_score_project",
default: "credit_scoring_aws",
projects: [
{
name: "Credit Score Project",
description: "Project for credit scoring team and associated models.",
id: "credit_score_project",
id: "credit_scoring_aws",
registryPath: "/registry.db", // Changed to match what the test expects
},
],
Expand Down
105 changes: 90 additions & 15 deletions ui/src/pages/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,19 @@ const Layout = () => {
});

const registryPath = currentProject?.registryPath || "";
const { data } = useLoadRegistry(registryPath);

// For global search, use the first available registry path (typically all projects share the same registry)
// If projects have different registries, we use the first one as the "global" registry
const globalRegistryPath =
projectsData?.projects?.[0]?.registryPath || registryPath;

// Load filtered data for current project (for sidebar and page-level search)
const { data } = useLoadRegistry(registryPath, projectName);

// Load unfiltered data for global search (across all projects)
const { data: globalData } = useLoadRegistry(globalRegistryPath);

// Categories for page-level search (filtered to current project)
const categories = data
? [
{
Expand Down Expand Up @@ -84,31 +95,92 @@ const Layout = () => {
]
: [];

// Helper function to extract project ID from an item
const getProjectId = (item: any): string => {
// Try different possible locations for the project field
return item?.spec?.project || item?.project || projectName || "unknown";
};

// Categories for global search (includes all projects)
const globalCategories = globalData
? [
{
name: "Data Sources",
data: (globalData.objects.dataSources || []).map((item: any) => ({
...item,
projectId: getProjectId(item),
})),
getLink: (item: any) => {
const project = item?.projectId || getProjectId(item);
return `/p/${project}/data-source/${item.name}`;
},
},
{
name: "Entities",
data: (globalData.objects.entities || []).map((item: any) => ({
...item,
projectId: getProjectId(item),
})),
getLink: (item: any) => {
const project = item?.projectId || getProjectId(item);
return `/p/${project}/entity/${item.name}`;
},
},
{
name: "Features",
data: (globalData.allFeatures || []).map((item: any) => ({
...item,
projectId: getProjectId(item),
})),
getLink: (item: any) => {
const featureView = item?.featureView;
const project = item?.projectId || getProjectId(item);
return featureView
? `/p/${project}/feature-view/${featureView}/feature/${item.name}`
: "#";
},
},
{
name: "Feature Views",
data: (globalData.mergedFVList || []).map((item: any) => ({
...item,
projectId: getProjectId(item),
})),
getLink: (item: any) => {
const project = item?.projectId || getProjectId(item);
return `/p/${project}/feature-view/${item.name}`;
},
},
{
name: "Feature Services",
data: (globalData.objects.featureServices || []).map((item: any) => ({
...item,
projectId: getProjectId(item),
})),
getLink: (item: any) => {
const serviceName = item?.name || item?.spec?.name;
const project = item?.projectId || getProjectId(item);
return serviceName
? `/p/${project}/feature-service/${serviceName}`
: "#";
},
},
]
: [];

const handleSearchOpen = () => {
console.log("Opening command palette - before state update"); // Debug log
setIsCommandPaletteOpen(true);
console.log("Command palette state should be updated to true");
};

useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
console.log(
"Layout key pressed:",
event.key,
"metaKey:",
event.metaKey,
"ctrlKey:",
event.ctrlKey,
);
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
console.log("Layout detected Cmd+K, preventing default");
event.preventDefault();
event.stopPropagation();
handleSearchOpen();
}
};

console.log("Layout adding keydown event listener");
window.addEventListener("keydown", handleKeyDown, true);
return () => {
window.removeEventListener("keydown", handleKeyDown, true);
Expand All @@ -121,7 +193,7 @@ const Layout = () => {
<CommandPalette
isOpen={isCommandPaletteOpen}
onClose={() => setIsCommandPaletteOpen(false)}
categories={categories}
categories={globalCategories}
/>
<EuiPage paddingSize="none" style={{ background: "transparent" }}>
<EuiPageSidebar
Expand Down Expand Up @@ -179,7 +251,10 @@ const Layout = () => {
grow={false}
style={{ width: "600px", maxWidth: "90%" }}
>
<RegistrySearch ref={searchRef} categories={categories} />
<RegistrySearch
ref={searchRef}
categories={globalCategories}
/>
</EuiFlexItem>
</EuiFlexGroup>
</div>
Expand Down
Loading
Loading