diff --git a/ui/src/components/RegistrySearch.tsx b/ui/src/components/RegistrySearch.tsx new file mode 100644 index 00000000000..b859249963b --- /dev/null +++ b/ui/src/components/RegistrySearch.tsx @@ -0,0 +1,90 @@ +import React, { useState } from "react"; +import { EuiText, EuiFieldSearch, EuiSpacer } from "@elastic/eui"; +import EuiCustomLink from "./EuiCustomLink"; + +interface RegistrySearchProps { + categories: { + name: string; + data: any[]; + getLink: (item: any) => string; + }[]; +} + +const RegistrySearch: React.FC = ({ categories }) => { + const [searchText, setSearchText] = useState(""); + + const searchResults = categories.map(({ name, data, getLink }) => { + const filteredItems = searchText + ? data.filter((item) => { + const itemName = + "name" in item + ? String(item.name) + : "spec" in item && item.spec && "name" in item.spec + ? String(item.spec.name ?? "Unknown") + : "Unknown"; + + return itemName.toLowerCase().includes(searchText.toLowerCase()); + }) + : []; + + return { name, items: filteredItems, getLink }; + }); + + return ( + <> + + +

Search in registry

+
+ + setSearchText(e.target.value)} + isClearable + fullWidth + /> + + + {searchText && ( + +

Search Results

+ {searchResults.some(({ items }) => items.length > 0) ? ( + searchResults.map(({ name, items, getLink }, index) => + items.length > 0 ? ( +
+

{name}

+
    + {items.map((item, idx) => { + const itemName = + "name" in item + ? item.name + : "spec" in item + ? item.spec?.name + : "Unknown"; + + const itemLink = getLink(item); + + return ( +
  • + + {itemName} + +
  • + ); + })} +
+ +
+ ) : null, + ) + ) : ( +

No matches found.

+ )} +
+ )} + + ); +}; + +export default RegistrySearch; diff --git a/ui/src/pages/ProjectOverviewPage.tsx b/ui/src/pages/ProjectOverviewPage.tsx index 427a33238ab..b71e2363494 100644 --- a/ui/src/pages/ProjectOverviewPage.tsx +++ b/ui/src/pages/ProjectOverviewPage.tsx @@ -1,5 +1,4 @@ -import React, { useContext } from "react"; - +import React, { useContext, useState } from "react"; import { EuiPageTemplate, EuiText, @@ -9,6 +8,7 @@ import { EuiSpacer, EuiSkeletonText, EuiEmptyPrompt, + EuiFieldSearch, } from "@elastic/eui"; import { useDocumentTitle } from "../hooks/useDocumentTitle"; @@ -16,12 +16,56 @@ import ObjectsCountStats from "../components/ObjectsCountStats"; import ExplorePanel from "../components/ExplorePanel"; import useLoadRegistry from "../queries/useLoadRegistry"; import RegistryPathContext from "../contexts/RegistryPathContext"; +import RegistrySearch from "../components/RegistrySearch"; +import { useParams } from "react-router-dom"; const ProjectOverviewPage = () => { useDocumentTitle("Feast Home"); const registryUrl = useContext(RegistryPathContext); const { isLoading, isSuccess, isError, data } = useLoadRegistry(registryUrl); + const [searchText, setSearchText] = useState(""); + + const { projectName } = useParams<{ projectName: string }>(); + + const categories = [ + { + name: "Data Sources", + data: data?.objects.dataSources || [], + getLink: (item: any) => `/p/${projectName}/data-source/${item.name}`, + }, + { + name: "Entities", + data: data?.objects.entities || [], + getLink: (item: any) => `/p/${projectName}/entity/${item.name}`, + }, + { + name: "Features", + data: data?.allFeatures || [], + getLink: (item: any) => { + const featureView = item?.featureView; + return featureView + ? `/p/${projectName}/feature-view/${featureView}/feature/${item.name}` + : "#"; + }, + }, + { + name: "Feature Views", + data: data?.mergedFVList || [], + getLink: (item: any) => `/p/${projectName}/feature-view/${item.name}`, + }, + { + name: "Feature Services", + data: data?.objects.featureServices || [], + getLink: (item: any) => { + const serviceName = item?.name || item?.spec?.name; + return serviceName + ? `/p/${projectName}/feature-service/${serviceName}` + : "#"; + }, + }, + ]; + return ( @@ -59,8 +103,8 @@ const ProjectOverviewPage = () => {

Welcome to your new Feast project. In this UI, you can see - Data Sources, Entities, Feature Views and Feature Services - registered in Feast. + Data Sources, Entities, Features, Feature Views, and Feature + Services registered in Feast.

It looks like this project already has some objects @@ -85,6 +129,9 @@ const ProjectOverviewPage = () => { + + {isSuccess && } + ); }; diff --git a/ui/src/pages/features/FeatureListPage.tsx b/ui/src/pages/features/FeatureListPage.tsx index 6eb800c2c23..572ef07f450 100644 --- a/ui/src/pages/features/FeatureListPage.tsx +++ b/ui/src/pages/features/FeatureListPage.tsx @@ -12,6 +12,7 @@ import EuiCustomLink from "../../components/EuiCustomLink"; import { useParams } from "react-router-dom"; import useLoadRegistry from "../../queries/useLoadRegistry"; import RegistryPathContext from "../../contexts/RegistryPathContext"; +import { FeatureIcon } from "../../graphics/FeatureIcon"; interface Feature { name: string; @@ -35,9 +36,6 @@ const FeatureListPage = () => { const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(100); - if (isLoading) return

Loading...

; - if (isError) return

Error loading features.

; - const features: Feature[] = data?.allFeatures || []; const filteredFeatures = features.filter((feature) => @@ -107,24 +105,36 @@ const FeatureListPage = () => { return ( - + - setSearchText(e.target.value)} - fullWidth - /> - + {isLoading ? ( +

Loading...

+ ) : isError ? ( +

We encountered an error while loading.

+ ) : ( + <> + setSearchText(e.target.value)} + fullWidth + /> + + + )}
);