From 598abc2096365807b23313c7fa60f86819497645 Mon Sep 17 00:00:00 2001 From: ntkathole Date: Thu, 27 Mar 2025 18:18:18 +0530 Subject: [PATCH 1/4] feat: Added global search feature at UI homepage Signed-off-by: ntkathole --- ui/src/pages/ProjectOverviewPage.tsx | 75 ++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/ui/src/pages/ProjectOverviewPage.tsx b/ui/src/pages/ProjectOverviewPage.tsx index 427a33238ab..d24eea7d232 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"; @@ -22,6 +22,32 @@ const ProjectOverviewPage = () => { const registryUrl = useContext(RegistryPathContext); const { isLoading, isSuccess, isError, data } = useLoadRegistry(registryUrl); + const [searchText, setSearchText] = useState(""); + + const categories = [ + { name: "Data Sources", data: data?.objects.dataSources || [] }, + { name: "Entities", data: data?.objects.entities || [] }, + { name: "Features", data: data?.allFeatures || [] }, + { name: "Feature Views", data: data?.mergedFVList || [] }, + { name: "Feature Services", data: data?.objects.featureServices || [] }, + ]; + + const searchResults = categories.map(({ name, data }) => { + 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 }; + }); + return ( @@ -59,7 +85,7 @@ const ProjectOverviewPage = () => {

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

@@ -85,6 +111,49 @@ const ProjectOverviewPage = () => { + + + + +

Search in Registry

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

Search Results

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

{name}

+
    + {items.map((item, idx) => ( +
  • + {"name" in item + ? item.name + : "spec" in item + ? item.spec?.name + : "Unknown"} +
  • + ))} +
+
+ ) : null, + ) + ) : ( +

No matches found.

+ )} +
+ )} +
); }; From d52414ab9d03e61f80a010abef8c4b6bf10546fa Mon Sep 17 00:00:00 2001 From: ntkathole Date: Thu, 27 Mar 2025 22:05:53 +0530 Subject: [PATCH 2/4] feat: Made searched items linkable Signed-off-by: ntkathole --- ui/src/components/RegistrySearch.tsx | 88 ++++++++++++++++++++++++++++ ui/src/pages/ProjectOverviewPage.tsx | 73 ++++++++++++++++++----- 2 files changed, 146 insertions(+), 15 deletions(-) create mode 100644 ui/src/components/RegistrySearch.tsx diff --git a/ui/src/components/RegistrySearch.tsx b/ui/src/components/RegistrySearch.tsx new file mode 100644 index 00000000000..8d85206ab53 --- /dev/null +++ b/ui/src/components/RegistrySearch.tsx @@ -0,0 +1,88 @@ +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 d24eea7d232..cfe5799e56a 100644 --- a/ui/src/pages/ProjectOverviewPage.tsx +++ b/ui/src/pages/ProjectOverviewPage.tsx @@ -16,6 +16,8 @@ import ObjectsCountStats from "../components/ObjectsCountStats"; import ExplorePanel from "../components/ExplorePanel"; import useLoadRegistry from "../queries/useLoadRegistry"; import RegistryPathContext from "../contexts/RegistryPathContext"; +import EuiCustomLink from "../components/EuiCustomLink"; +import { useParams } from "react-router-dom"; const ProjectOverviewPage = () => { useDocumentTitle("Feast Home"); @@ -24,12 +26,44 @@ const ProjectOverviewPage = () => { const [searchText, setSearchText] = useState(""); + const { projectName } = useParams<{ projectName: string }>(); + const categories = [ - { name: "Data Sources", data: data?.objects.dataSources || [] }, - { name: "Entities", data: data?.objects.entities || [] }, - { name: "Features", data: data?.allFeatures || [] }, - { name: "Feature Views", data: data?.mergedFVList || [] }, - { name: "Feature Services", data: data?.objects.featureServices || [] }, + { + 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}` + : "#"; + }, + }, ]; const searchResults = categories.map(({ name, data }) => { @@ -85,8 +119,8 @@ const ProjectOverviewPage = () => {

Welcome to your new Feast project. In this UI, you can see - Data Sources, Entities, Features, 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 @@ -130,20 +164,29 @@ const ProjectOverviewPage = () => {

Search Results

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

{name}

    - {items.map((item, idx) => ( -
  • - {"name" in item + {items.map((item, idx) => { + const itemName = + "name" in item ? item.name : "spec" in item ? item.spec?.name - : "Unknown"} -
  • - ))} + : "Unknown"; + + const itemLink = categories[index].getLink(item); + + return ( +
  • + + {itemName} + +
  • + ); + })}
) : null, From c3f952ac9b3c0a209b986db9cfcc34816e69d034 Mon Sep 17 00:00:00 2001 From: ntkathole Date: Thu, 27 Mar 2025 22:23:05 +0530 Subject: [PATCH 3/4] feat: RegistrySearch component Signed-off-by: ntkathole --- ui/src/pages/ProjectOverviewPage.tsx | 69 +--------------------------- 1 file changed, 2 insertions(+), 67 deletions(-) diff --git a/ui/src/pages/ProjectOverviewPage.tsx b/ui/src/pages/ProjectOverviewPage.tsx index cfe5799e56a..e86ff803610 100644 --- a/ui/src/pages/ProjectOverviewPage.tsx +++ b/ui/src/pages/ProjectOverviewPage.tsx @@ -16,7 +16,7 @@ import ObjectsCountStats from "../components/ObjectsCountStats"; import ExplorePanel from "../components/ExplorePanel"; import useLoadRegistry from "../queries/useLoadRegistry"; import RegistryPathContext from "../contexts/RegistryPathContext"; -import EuiCustomLink from "../components/EuiCustomLink"; +import RegistrySearch from "../components/RegistrySearch"; import { useParams } from "react-router-dom"; const ProjectOverviewPage = () => { @@ -66,22 +66,6 @@ const ProjectOverviewPage = () => { }, ]; - const searchResults = categories.map(({ name, data }) => { - 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 }; - }); - return ( @@ -145,57 +129,8 @@ const ProjectOverviewPage = () => { - - - -

Search in Registry

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

Search Results

- {searchResults.some(({ items }) => items.length > 0) ? ( - searchResults.map(({ name, items }, 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 = categories[index].getLink(item); - - return ( -
  • - - {itemName} - -
  • - ); - })} -
-
- ) : null, - ) - ) : ( -

No matches found.

- )} -
- )} +
); From 68e95680fa2e33aa2977cad4081915f60f3042da Mon Sep 17 00:00:00 2001 From: ntkathole Date: Thu, 27 Mar 2025 22:42:16 +0530 Subject: [PATCH 4/4] fix: fix load elements only after registry load Signed-off-by: ntkathole --- ui/src/components/RegistrySearch.tsx | 2 + ui/src/pages/ProjectOverviewPage.tsx | 2 +- ui/src/pages/features/FeatureListPage.tsx | 50 ++++++++++++++--------- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/ui/src/components/RegistrySearch.tsx b/ui/src/components/RegistrySearch.tsx index 8d85206ab53..b859249963b 100644 --- a/ui/src/components/RegistrySearch.tsx +++ b/ui/src/components/RegistrySearch.tsx @@ -36,6 +36,7 @@ const RegistrySearch: React.FC = ({ categories }) => {

Search in registry

+ = ({ categories }) => { ); })} +
) : null, ) diff --git a/ui/src/pages/ProjectOverviewPage.tsx b/ui/src/pages/ProjectOverviewPage.tsx index e86ff803610..b71e2363494 100644 --- a/ui/src/pages/ProjectOverviewPage.tsx +++ b/ui/src/pages/ProjectOverviewPage.tsx @@ -130,7 +130,7 @@ 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 + /> + + + )}
);