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
90 changes: 90 additions & 0 deletions ui/src/components/RegistrySearch.tsx
Original file line number Diff line number Diff line change
@@ -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<RegistrySearchProps> = ({ 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 (
<>
<EuiSpacer size="l" />
<EuiText>
<h3>Search in registry</h3>
</EuiText>
<EuiSpacer size="s" />
<EuiFieldSearch
placeholder="Search across Feature Views, Features, Entities, etc."
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
isClearable
fullWidth
/>
<EuiSpacer size="m" />

{searchText && (
<EuiText>
<h3>Search Results</h3>
{searchResults.some(({ items }) => items.length > 0) ? (
searchResults.map(({ name, items, getLink }, index) =>
items.length > 0 ? (
<div key={index}>
<h4>{name}</h4>
<ul>
{items.map((item, idx) => {
const itemName =
"name" in item
? item.name
: "spec" in item
? item.spec?.name
: "Unknown";

const itemLink = getLink(item);

return (
<li key={idx}>
<EuiCustomLink to={itemLink}>
{itemName}
</EuiCustomLink>
</li>
);
})}
</ul>
<EuiSpacer size="m" />
</div>
) : null,
)
) : (
<p>No matches found.</p>
)}
</EuiText>
)}
</>
);
};

export default RegistrySearch;
55 changes: 51 additions & 4 deletions ui/src/pages/ProjectOverviewPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { useContext } from "react";

import React, { useContext, useState } from "react";
import {
EuiPageTemplate,
EuiText,
Expand All @@ -9,19 +8,64 @@ import {
EuiSpacer,
EuiSkeletonText,
EuiEmptyPrompt,
EuiFieldSearch,
} from "@elastic/eui";

import { useDocumentTitle } from "../hooks/useDocumentTitle";
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 (
<EuiPageTemplate panelled>
<EuiPageTemplate.Section>
Expand Down Expand Up @@ -59,8 +103,8 @@ const ProjectOverviewPage = () => {
<EuiText>
<p>
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.
</p>
<p>
It looks like this project already has some objects
Expand All @@ -85,6 +129,9 @@ const ProjectOverviewPage = () => {
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageTemplate.Section>
<EuiPageTemplate.Section>
{isSuccess && <RegistrySearch categories={categories} />}
</EuiPageTemplate.Section>
</EuiPageTemplate>
);
};
Expand Down
50 changes: 30 additions & 20 deletions ui/src/pages/features/FeatureListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -35,9 +36,6 @@ const FeatureListPage = () => {
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(100);

if (isLoading) return <p>Loading...</p>;
if (isError) return <p>Error loading features.</p>;

const features: Feature[] = data?.allFeatures || [];

const filteredFeatures = features.filter((feature) =>
Expand Down Expand Up @@ -107,24 +105,36 @@ const FeatureListPage = () => {

return (
<EuiPageTemplate panelled>
<EuiPageTemplate.Header pageTitle="Feature List" />
<EuiPageTemplate.Header
restrictWidth
iconType={FeatureIcon}
pageTitle="Feature List"
/>
<EuiPageTemplate.Section>
<EuiFieldSearch
placeholder="Search features"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
fullWidth
/>
<EuiBasicTable
columns={columns}
items={paginatedFeatures}
rowProps={getRowProps}
sorting={{
sort: { field: sortField, direction: sortDirection },
}}
onChange={onTableChange}
pagination={pagination}
/>
{isLoading ? (
<p>Loading...</p>
) : isError ? (
<p>We encountered an error while loading.</p>
) : (
<>
<EuiFieldSearch
placeholder="Search features"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
fullWidth
/>
<EuiBasicTable
columns={columns}
items={paginatedFeatures}
rowProps={getRowProps}
sorting={{
sort: { field: sortField, direction: sortDirection },
}}
onChange={onTableChange}
pagination={pagination}
/>
</>
)}
</EuiPageTemplate.Section>
</EuiPageTemplate>
);
Expand Down