From f94d0887db30d262a1ba1c2bee47da29584c50ed Mon Sep 17 00:00:00 2001 From: hao-affirm <104030690+hao-affirm@users.noreply.github.com> Date: Tue, 27 Sep 2022 12:03:21 -0700 Subject: [PATCH 1/8] add stream feature view to ui Signed-off-by: hao-affirm <104030690+hao-affirm@users.noreply.github.com> --- ui/public/registry.json | 66 +++++++++ ui/src/custom-tabs/TabsRegistryContext.tsx | 25 ++++ .../stream-fv-demo-tab/DemoCustomTab.tsx | 85 +++++++++++ .../stream-fv-demo-tab/useDemoQuery.tsx | 44 ++++++ ui/src/custom-tabs/types.ts | 21 +++ ui/src/index.tsx | 10 +- .../feature-views/FeatureViewInstance.tsx | 8 ++ .../feature-views/FeatureViewListingTable.tsx | 2 +- .../StreamFeatureViewInstance.tsx | 69 +++++++++ .../StreamFeatureViewOverviewTab.tsx | 133 ++++++++++++++++++ .../pages/feature-views/useLoadFeatureView.ts | 19 ++- ui/src/parsers/feastRegistry.ts | 2 + ui/src/parsers/feastSFVS.ts | 77 ++++++++++ ui/src/parsers/mergedFVTypes.ts | 25 +++- ui/src/parsers/parseEntityRelationships.ts | 30 ++++ ...reamFeatureViewCustomTabLoadingWrapper.tsx | 46 ++++++ 16 files changed, 657 insertions(+), 5 deletions(-) create mode 100644 ui/src/custom-tabs/stream-fv-demo-tab/DemoCustomTab.tsx create mode 100644 ui/src/custom-tabs/stream-fv-demo-tab/useDemoQuery.tsx create mode 100644 ui/src/pages/feature-views/StreamFeatureViewInstance.tsx create mode 100644 ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx create mode 100644 ui/src/parsers/feastSFVS.ts create mode 100644 ui/src/utils/custom-tabs/StreamFeatureViewCustomTabLoadingWrapper.tsx diff --git a/ui/public/registry.json b/ui/public/registry.json index 2d5c93c9620..1c5d2cf4d7e 100644 --- a/ui/public/registry.json +++ b/ui/public/registry.json @@ -630,5 +630,71 @@ } } ], + "streamFeatureViews": [ + { + "meta": { + "createdTimestamp": "2022-05-11T19:27:03.171556Z", + "lastUpdatedTimestamp": "2022-05-11T19:27:03.171556Z" + }, + "spec": { + "features": [ + { + "name": "transaction_gt_last_credit_card_due", + "valueType": "BOOL" + } + ], + "name": "transaction_gt_last_credit_card_due", + "sources": { + "credit_history": { + "featureViewProjection": { + "featureColumns": [ + { + "name": "credit_card_due", + "valueType": "INT64" + }, + { + "name": "mortgage_due", + "valueType": "INT64" + }, + { + "name": "student_loan_due", + "valueType": "INT64" + }, + { + "name": "vehicle_loan_due", + "valueType": "INT64" + }, + { + "name": "hard_pulls", + "valueType": "INT64" + }, + { + "name": "missed_payments_2y", + "valueType": "INT64" + }, + { + "name": "missed_payments_1y", + "valueType": "INT64" + }, + { + "name": "missed_payments_6m", + "valueType": "INT64" + }, + { + "name": "bankruptcies", + "valueType": "INT64" + } + ], + "featureViewName": "credit_history" + } + }, + }, + "userDefinedFunction": { + "body": "@on_demand_feature_view(\n sources=[credit_history, input_request],\n schema=[\n Field(name=\"transaction_gt_last_credit_card_due\", dtype=Bool),\n ],\n)\ndef transaction_gt_last_credit_card_due(inputs: pd.DataFrame) -> pd.DataFrame:\n df = pd.DataFrame()\n df[\"transaction_gt_last_credit_card_due\"] = (\n inputs[\"transaction_amt\"] > inputs[\"credit_card_due\"]\n )\n return df\n", + "name": "transaction_gt_last_credit_card_due" + } + } + } + ], "project": "credit_scoring_aws" } diff --git a/ui/src/custom-tabs/TabsRegistryContext.tsx b/ui/src/custom-tabs/TabsRegistryContext.tsx index 9f493e6d11b..83820de1535 100644 --- a/ui/src/custom-tabs/TabsRegistryContext.tsx +++ b/ui/src/custom-tabs/TabsRegistryContext.tsx @@ -10,6 +10,7 @@ import { import RegularFeatureViewCustomTabLoadingWrapper from "../utils/custom-tabs/RegularFeatureViewCustomTabLoadingWrapper"; import OnDemandFeatureViewCustomTabLoadingWrapper from "../utils/custom-tabs/OnDemandFeatureViewCustomTabLoadingWrapper"; +import StreamFeatureViewCustomTabLoadingWrapper from "../utils/custom-tabs/StreamFeatureViewCustomTabLoadingWrapper"; import FeatureServiceCustomTabLoadingWrapper from "../utils/custom-tabs/FeatureServiceCustomTabLoadingWrapper"; import FeatureCustomTabLoadingWrapper from "../utils/custom-tabs/FeatureCustomTabLoadingWrapper"; import DataSourceCustomTabLoadingWrapper from "../utils/custom-tabs/DataSourceCustomTabLoadingWrapper"; @@ -19,6 +20,7 @@ import DatasetCustomTabLoadingWrapper from "../utils/custom-tabs/DatasetCustomTa import { RegularFeatureViewCustomTabRegistrationInterface, OnDemandFeatureViewCustomTabRegistrationInterface, + StreamFeatureViewCustomTabRegistrationInterface, FeatureServiceCustomTabRegistrationInterface, FeatureCustomTabRegistrationInterface, DataSourceCustomTabRegistrationInterface, @@ -30,6 +32,7 @@ import { interface FeastTabsRegistryInterface { RegularFeatureViewCustomTabs?: RegularFeatureViewCustomTabRegistrationInterface[]; OnDemandFeatureViewCustomTabs?: OnDemandFeatureViewCustomTabRegistrationInterface[]; + StreamFeatureViewCustomTabs?: StreamFeatureViewCustomTabRegistrationInterface[]; FeatureServiceCustomTabs?: FeatureServiceCustomTabRegistrationInterface[]; FeatureCustomTabs?: FeatureCustomTabRegistrationInterface[]; DataSourceCustomTabs?: DataSourceCustomTabRegistrationInterface[]; @@ -148,6 +151,16 @@ const useOnDemandFeatureViewCustomTabs = (navigate: NavigateFunction) => { ); }; +const useStreamFeatureViewCustomTabs = (navigate: NavigateFunction) => { + const { StreamFeatureViewCustomTabs } = + React.useContext(TabsRegistryContext); + + return useGenericCustomTabsNavigation( + StreamFeatureViewCustomTabs || [], + navigate + ); +}; + const useFeatureServiceCustomTabs = (navigate: NavigateFunction) => { const { FeatureServiceCustomTabs } = React.useContext(TabsRegistryContext); @@ -214,6 +227,16 @@ const useOnDemandFeatureViewCustomTabRoutes = () => { ); }; +const useStreamFeatureViewCustomTabRoutes = () => { + const { StreamFeatureViewCustomTabs } = + React.useContext(TabsRegistryContext); + + return genericCustomTabRoutes( + StreamFeatureViewCustomTabs || [], + StreamFeatureViewCustomTabLoadingWrapper + ); +}; + const useFeatureServiceCustomTabRoutes = () => { const { FeatureServiceCustomTabs } = React.useContext(TabsRegistryContext); @@ -264,6 +287,7 @@ export { // Navigation useRegularFeatureViewCustomTabs, useOnDemandFeatureViewCustomTabs, + useStreamFeatureViewCustomTabs, useFeatureServiceCustomTabs, useFeatureCustomTabs, useDataSourceCustomTabs, @@ -272,6 +296,7 @@ export { // Routes useRegularFeatureViewCustomTabRoutes, useOnDemandFeatureViewCustomTabRoutes, + useStreamFeatureViewCustomTabRoutes, useFeatureServiceCustomTabRoutes, useFeatureCustomTabRoutes, useDataSourceCustomTabRoutes, diff --git a/ui/src/custom-tabs/stream-fv-demo-tab/DemoCustomTab.tsx b/ui/src/custom-tabs/stream-fv-demo-tab/DemoCustomTab.tsx new file mode 100644 index 00000000000..86e59d10c71 --- /dev/null +++ b/ui/src/custom-tabs/stream-fv-demo-tab/DemoCustomTab.tsx @@ -0,0 +1,85 @@ +import React from "react"; + +import { + // Feature View Custom Tabs will get these props + StreamFeatureViewCustomTabProps, +} from "../types"; + +import { + EuiLoadingContent, + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiCode, + EuiSpacer, +} from "@elastic/eui"; + +// Separating out the query is not required, +// but encouraged for code readability +import useDemoQuery from "./useDemoQuery"; + +const DemoCustomTab = ({ + id, + feastObjectQuery, +}: StreamFeatureViewCustomTabProps) => { + // Use React Query to fetch data + // that is custom to this tab. + // See: https://react-query.tanstack.com/guides/queries + const { isLoading, isError, isSuccess, data } = useDemoQuery({ + featureView: id, + }); + + if (isLoading) { + // Handle Loading State + // https://elastic.github.io/eui/#/display/loading + return ; + } + + if (isError) { + // Handle Data Fetching Error + // https://elastic.github.io/eui/#/display/empty-prompt + return ( + Unable to load your demo page} + body={ +

+ There was an error loading the Dashboard application. Contact your + administrator for help. +

+ } + /> + ); + } + + // Feast UI uses the Elastic UI component system. + // and are particularly + // useful for layouts. + return ( + + + +

Hello World. The following is fetched data.

+ + {isSuccess && data && ( + +
{JSON.stringify(data, null, 2)}
+
+ )} +
+ +

... and this is data from Feast UI’s own query.

+ + {feastObjectQuery.isSuccess && feastObjectQuery.data && ( + +
{JSON.stringify(feastObjectQuery.data, null, 2)}
+
+ )} +
+
+
+ ); +}; + +export default DemoCustomTab; diff --git a/ui/src/custom-tabs/stream-fv-demo-tab/useDemoQuery.tsx b/ui/src/custom-tabs/stream-fv-demo-tab/useDemoQuery.tsx new file mode 100644 index 00000000000..b93602dbe3b --- /dev/null +++ b/ui/src/custom-tabs/stream-fv-demo-tab/useDemoQuery.tsx @@ -0,0 +1,44 @@ +import { useQuery } from "react-query"; +import { z } from "zod"; + +// Use Zod to check the shape of the +// json object being loaded +const demoSchema = z.object({ + hello: z.string(), + name: z.string().optional(), +}); + +// Make the type of the object available +type DemoDataType = z.infer; + +interface DemoQueryInterface { + featureView: string | undefined; +} + +const useDemoQuery = ({ featureView }: DemoQueryInterface) => { + // React Query manages caching for you based on query keys + // See: https://react-query.tanstack.com/guides/query-keys + const queryKey = `demo-tab-namespace:${featureView}`; + + // Pass the type to useQuery + // so that components consuming the + // result gets nice type hints + // on the other side. + return useQuery( + queryKey, + () => { + // Customizing the URL based on your needs + const url = `/demo-custom-tabs/demo.json`; + + return fetch(url) + .then((res) => res.json()) + .then((data) => demoSchema.parse(data)); // Use zod to parse results + }, + { + enabled: !!featureView, // Only start the query when the variable is not undefined + } + ); +}; + +export default useDemoQuery; +export type { DemoDataType }; diff --git a/ui/src/custom-tabs/types.ts b/ui/src/custom-tabs/types.ts index 1e555d6185c..ea1dbc8757b 100644 --- a/ui/src/custom-tabs/types.ts +++ b/ui/src/custom-tabs/types.ts @@ -1,5 +1,6 @@ import { useLoadOnDemandFeatureView, + useLoadStreamFeatureView, useLoadRegularFeatureView, } from "../pages/feature-views/useLoadFeatureView"; import useLoadFeature from "../pages/features/useLoadFeature"; @@ -48,6 +49,23 @@ interface OnDemandFeatureViewCustomTabRegistrationInterface }: OnDemandFeatureViewCustomTabProps) => JSX.Element; } +// Type for Stream Feature View Custom Tabs +type StreamFeatureViewQueryReturnType = ReturnType< + typeof useLoadStreamFeatureView +>; +interface StreamFeatureViewCustomTabProps { + id: string | undefined; + feastObjectQuery: StreamFeatureViewQueryReturnType; +} +interface StreamFeatureViewCustomTabRegistrationInterface + extends CustomTabRegistrationInterface { + Component: ({ + id, + feastObjectQuery, + ...args + }: StreamFeatureViewCustomTabProps) => JSX.Element; +} + // Type for Entity Custom Tabs interface EntityCustomTabProps { id: string | undefined; @@ -127,6 +145,9 @@ export type { OnDemandFeatureViewQueryReturnType, OnDemandFeatureViewCustomTabProps, OnDemandFeatureViewCustomTabRegistrationInterface, + StreamFeatureViewQueryReturnType, + StreamFeatureViewCustomTabProps, + StreamFeatureViewCustomTabRegistrationInterface, FeatureServiceCustomTabRegistrationInterface, FeatureServiceCustomTabProps, DataSourceCustomTabRegistrationInterface, diff --git a/ui/src/index.tsx b/ui/src/index.tsx index 2233b90c9e6..e38570929d4 100644 --- a/ui/src/index.tsx +++ b/ui/src/index.tsx @@ -18,6 +18,7 @@ import FeastUI from "./FeastUI"; import DataTab from "./custom-tabs/data-tab/DataTab"; import RFVDemoCustomTab from "./custom-tabs/reguar-fv-demo-tab/DemoCustomTab"; import ODFVDemoCustomTab from "./custom-tabs/ondemand-fv-demo-tab/DemoCustomTab"; +import SFVDemoCustomTab from "./custom-tabs/stream-fv-demo-tab/DemoCustomTab"; import FSDemoCustomTab from "./custom-tabs/feature-service-demo-tab/DemoCustomTab"; import DSDemoCustomTab from "./custom-tabs/data-source-demo-tab/DemoCustomTab"; import EntDemoCustomTab from "./custom-tabs/entity-demo-tab/DemoCustomTab"; @@ -46,6 +47,13 @@ const tabsRegistry = { Component: ODFVDemoCustomTab, }, ], + StreamFeatureViewCustomTabs: [ + { + label: "Custom Tab Demo", + path: "demo-tab", + Component: SFVDemoCustomTab, + }, + ], FeatureServiceCustomTabs: [ { label: "Custom Tab Demo", @@ -93,4 +101,4 @@ ReactDOM.render( /> , document.getElementById("root") -); \ No newline at end of file +); diff --git a/ui/src/pages/feature-views/FeatureViewInstance.tsx b/ui/src/pages/feature-views/FeatureViewInstance.tsx index b0fa7c32b03..5352507573f 100644 --- a/ui/src/pages/feature-views/FeatureViewInstance.tsx +++ b/ui/src/pages/feature-views/FeatureViewInstance.tsx @@ -7,8 +7,11 @@ import { FeastFeatureViewType } from "../../parsers/feastFeatureViews"; import RegularFeatureInstance from "./RegularFeatureViewInstance"; import { FEAST_FV_TYPES } from "../../parsers/mergedFVTypes"; import { FeastODFVType } from "../../parsers/feastODFVS"; +import { FeastSFVType } from "../../parsers/feastSFVS"; import useLoadFeatureView from "./useLoadFeatureView"; import OnDemandFeatureInstance from "./OnDemandFeatureViewInstance"; +import StreamFeatureInstance from "./StreamFeatureViewInstance"; + const FeatureViewInstance = () => { const { featureViewName } = useParams(); @@ -45,6 +48,11 @@ const FeatureViewInstance = () => { return ; } + if (data.type === FEAST_FV_TYPES.stream) { + const sfv: FeastSFVType = data.object; + + return ; + } } return

No Data So Sad

; diff --git a/ui/src/pages/feature-views/FeatureViewListingTable.tsx b/ui/src/pages/feature-views/FeatureViewListingTable.tsx index 59f8b1ed7aa..ceb756db804 100644 --- a/ui/src/pages/feature-views/FeatureViewListingTable.tsx +++ b/ui/src/pages/feature-views/FeatureViewListingTable.tsx @@ -35,7 +35,7 @@ const FeatureViewListingTable = ({ href={`/p/${projectName}/feature-view/${name}`} to={`/p/${projectName}/feature-view/${name}`} > - {name} {item.type === "ondemand" && ondemand} + {name} {(item.type === "ondemand" && ondemand) || (item.type === "stream" && stream)} ); }, diff --git a/ui/src/pages/feature-views/StreamFeatureViewInstance.tsx b/ui/src/pages/feature-views/StreamFeatureViewInstance.tsx new file mode 100644 index 00000000000..404134fee04 --- /dev/null +++ b/ui/src/pages/feature-views/StreamFeatureViewInstance.tsx @@ -0,0 +1,69 @@ +import React from "react"; +import { Route, Routes, useNavigate } from "react-router-dom"; +import { useParams } from "react-router-dom"; +import { + EuiPageHeader, + EuiPageContent, + EuiPageContentBody, +} from "@elastic/eui"; + +import { FeatureViewIcon32 } from "../../graphics/FeatureViewIcon"; +import { useMatchExact } from "../../hooks/useMatchSubpath"; +import { FeastODFVType } from "../../parsers/feastODFVS"; +import StreamFeatureViewOverviewTab from "./StreamFeatureViewOverviewTab"; + +import { + useStreamFeatureViewCustomTabs, + useStreamFeatureViewCustomTabRoutes, +} from "../../custom-tabs/TabsRegistryContext"; + +interface StreamFeatureInstanceProps { + data: FeastODFVType; +} + +const StreamFeatureInstance = ({ data }: StreamFeatureInstanceProps) => { + const navigate = useNavigate(); + let { featureViewName } = useParams(); + + const { customNavigationTabs } = useStreamFeatureViewCustomTabs(navigate); + const CustomTabRoutes = useStreamFeatureViewCustomTabRoutes(); + + return ( + + { + navigate(""); + }, + }, + ...customNavigationTabs, + ]} + /> + + + + } + /> + {CustomTabRoutes} + + + + + ); +}; + +export default StreamFeatureInstance; diff --git a/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx b/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx new file mode 100644 index 00000000000..4b891331424 --- /dev/null +++ b/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx @@ -0,0 +1,133 @@ +import { + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiText, + EuiTitle, + EuiPanel, + EuiCodeBlock, + EuiSpacer, +} from "@elastic/eui"; +import React from "react"; +import FeaturesListDisplay from "../../components/FeaturesListDisplay"; +import { + FeastODFVType, + FeatureViewProjectionType, +} from "../../parsers/feastODFVS"; +import { useParams } from "react-router-dom"; +import { EntityRelation } from "../../parsers/parseEntityRelationships"; +import { FEAST_FCO_TYPES } from "../../parsers/types"; +import useLoadRelationshipData from "../../queries/useLoadRelationshipsData"; +import FeatureViewProjectionDisplayPanel from "./components/FeatureViewProjectionDisplayPanel"; +import ConsumingFeatureServicesList from "./ConsumingFeatureServicesList"; + +interface StreamFeatureViewOverviewTabProps { + data: FeastODFVType; +} + +const whereFSconsumesThisFv = (fvName: string) => { + return (r: EntityRelation) => { + return ( + r.source.name === fvName && + r.target.type === FEAST_FCO_TYPES.featureService + ); + }; +}; + +const StreamFeatureViewOverviewTab = ({ + data, +}: StreamFeatureViewOverviewTabProps) => { + const inputs = Object.entries(data.spec.sources); + const { projectName } = useParams(); + + const relationshipQuery = useLoadRelationshipData(); + const fsNames = relationshipQuery.data + ? relationshipQuery.data + .filter(whereFSconsumesThisFv(data.spec.name)) + .map((fs) => { + return fs.target.name; + }) + : []; + + return ( + + + + + +

Transformation

+
+ + + {data.spec.userDefinedFunction.body} + +
+
+
+ + + + +

Features ({data.spec.features.length})

+
+ + {projectName && data.spec.features ? ( + + ) : ( + No Tags sepcified on this feature view. + )} +
+
+ + + +

Inputs ({inputs.length})

+
+ + + {inputs.map(([key, inputGroup]) => { + + if (inputGroup as FeatureViewProjectionType) { + return ( + + + + ); + } + + return ( + + + {JSON.stringify(inputGroup, null, 2)} + + + ); + })} + +
+ + + +

Consuming Feature Services

+
+ + {fsNames.length > 0 ? ( + + ) : ( + No services consume this feature view + )} +
+
+
+
+ ); +}; + +export default StreamFeatureViewOverviewTab; diff --git a/ui/src/pages/feature-views/useLoadFeatureView.ts b/ui/src/pages/feature-views/useLoadFeatureView.ts index ded7900ea94..7685171b72b 100644 --- a/ui/src/pages/feature-views/useLoadFeatureView.ts +++ b/ui/src/pages/feature-views/useLoadFeatureView.ts @@ -51,5 +51,22 @@ const useLoadOnDemandFeatureView = (featureViewName: string) => { }; }; +const useLoadStreamFeatureView = (featureViewName: string) => { + const registryUrl = useContext(RegistryPathContext); + const registryQuery = useLoadRegistry(registryUrl); + + const data = + registryQuery.data === undefined + ? undefined + : registryQuery.data.objects.streamFeatureViews?.find((fv) => { + return fv.spec.name === featureViewName; + }); + + return { + ...registryQuery, + data, + }; +}; + export default useLoadFeatureView; -export { useLoadRegularFeatureView, useLoadOnDemandFeatureView }; +export { useLoadRegularFeatureView, useLoadOnDemandFeatureView, useLoadStreamFeatureView }; diff --git a/ui/src/parsers/feastRegistry.ts b/ui/src/parsers/feastRegistry.ts index 98e4fccca2a..f84187046a8 100644 --- a/ui/src/parsers/feastRegistry.ts +++ b/ui/src/parsers/feastRegistry.ts @@ -5,6 +5,7 @@ import { FeastFeatureServiceSchema } from "./feastFeatureServices"; import { FeastFeatureViewSchema } from "./feastFeatureViews"; import { FeastSavedDatasetSchema } from "./feastSavedDataset"; import { FeastODFVSchema } from "./feastODFVS"; +import { FeastSFVSchema } from "./feastSFVS"; const FeastRegistrySchema = z.object({ project: z.string(), @@ -12,6 +13,7 @@ const FeastRegistrySchema = z.object({ entities: z.array(FeastEntitySchema).optional(), featureViews: z.array(FeastFeatureViewSchema).optional(), onDemandFeatureViews: z.array(FeastODFVSchema).optional(), + streamFeatureViews: z.array(FeastSFVSchema).optional(), featureServices: z.array(FeastFeatureServiceSchema).optional(), savedDatasets: z.array(FeastSavedDatasetSchema).optional(), }); diff --git a/ui/src/parsers/feastSFVS.ts b/ui/src/parsers/feastSFVS.ts new file mode 100644 index 00000000000..29d01737530 --- /dev/null +++ b/ui/src/parsers/feastSFVS.ts @@ -0,0 +1,77 @@ +import { z } from "zod"; +import { FeastFeatureColumnSchema } from "./feastFeatureViews"; + +const FeatureViewProjectionSchema = z.object({ + featureViewProjection: z.object({ + featureViewName: z.string(), + featureColumns: z.array(FeastFeatureColumnSchema), + }), +}); + +const RequestDataSourceSchema = z.object({ + requestDataSource: z.object({ + type: z.string(), + name: z.string(), + requestDataOptions: z.object({ + schema: z.array(FeastFeatureColumnSchema), + }), + }), +}); + +const StreamDataSourceSchema = z.object({ + streamDataSource: z.object({ + type: z.string(), + name: z.string(), + streamDataOptions: z.object({ + schema: z.array(FeastFeatureColumnSchema), + }), + }), +}); + +const ODFVInputsSchema = z.union([ + FeatureViewProjectionSchema, + StreamDataSourceSchema, +]); + +const SFVInputsSchema = z.union([ + FeatureViewProjectionSchema, + StreamDataSourceSchema, +]); + +const FeastODFVSchema = z.object({ + spec: z.object({ + name: z.string(), + features: z.array(FeastFeatureColumnSchema), + sources: z.record(ODFVInputsSchema), + userDefinedFunction: z.object({ + name: z.string(), + body: z.string(), + }), + }), + meta: z.object({ + createdTimestamp: z.string().transform((val) => new Date(val)), + lastUpdatedTimestamp: z.string().transform((val) => new Date(val)), + }), +}); + +const FeastSFVSchema = z.object({ + spec: z.object({ + name: z.string(), + features: z.array(FeastFeatureColumnSchema), + sources: z.record(SFVInputsSchema), + userDefinedFunction: z.object({ + name: z.string(), + body: z.string(), + }), + }), + meta: z.object({ + createdTimestamp: z.string().transform((val) => new Date(val)), + lastUpdatedTimestamp: z.string().transform((val) => new Date(val)), + }), +}); + +type FeastSFVType = z.infer; +type StreamDataSourceType = z.infer; + +export { FeastSFVSchema }; +export type { FeastSFVType, StreamDataSourceType}; diff --git a/ui/src/parsers/mergedFVTypes.ts b/ui/src/parsers/mergedFVTypes.ts index 6a53b18e94d..edf1adee9e5 100644 --- a/ui/src/parsers/mergedFVTypes.ts +++ b/ui/src/parsers/mergedFVTypes.ts @@ -3,11 +3,13 @@ import { FeastFeatureViewType, } from "./feastFeatureViews"; import { FeastODFVType } from "./feastODFVS"; +import { FeastSFVType } from "./feastSFVS"; import { FeastRegistryType } from "./feastRegistry"; enum FEAST_FV_TYPES { regular = "regular", ondemand = "ondemand", + stream = "stream" } interface regularFVInterface { @@ -24,7 +26,14 @@ interface ODFVInterface { object: FeastODFVType; } -type genericFVType = regularFVInterface | ODFVInterface; +interface SFVInterface { + name: string; + type: FEAST_FV_TYPES.stream; + features: FeastFeatureColumnType[]; + object: FeastSFVType; +} + +type genericFVType = regularFVInterface | ODFVInterface | SFVInterface; const mergedFVTypes = (objects: FeastRegistryType) => { const mergedFVMap: Record = {}; @@ -55,9 +64,21 @@ const mergedFVTypes = (objects: FeastRegistryType) => { mergedFVList.push(obj); }); + objects.streamFeatureViews?.forEach((sfv) => { + const obj: genericFVType = { + name: sfv.spec.name, + type: FEAST_FV_TYPES.stream, + features: sfv.spec.features, + object: sfv, + }; + + mergedFVMap[sfv.spec.name] = obj; + mergedFVList.push(obj); + }); + return { mergedFVMap, mergedFVList }; }; export default mergedFVTypes; export { FEAST_FV_TYPES }; -export type { genericFVType, regularFVInterface, ODFVInterface }; +export type { genericFVType, regularFVInterface, ODFVInterface, SFVInterface }; diff --git a/ui/src/parsers/parseEntityRelationships.ts b/ui/src/parsers/parseEntityRelationships.ts index f54bff63a1c..0cf9ea783e5 100644 --- a/ui/src/parsers/parseEntityRelationships.ts +++ b/ui/src/parsers/parseEntityRelationships.ts @@ -86,6 +86,36 @@ const parseEntityRelationships = (objects: FeastRegistryType) => { }); } }); + objects.streamFeatureViews?.forEach((fv) => { + Object.values(fv.spec.sources).forEach((input: { [key: string]: any }) => { + if (input.requestDataSource) { + links.push({ + source: { + type: FEAST_FCO_TYPES["dataSource"], + name: input.streamSource.name, + }, + target: { + type: FEAST_FCO_TYPES["featureView"], + name: fv.spec.name, + }, + }); + } else if (input.featureViewProjection?.featureViewName) { + const source_fv = objects.featureViews?.find(el => el.spec.name === input.featureViewProjection.featureViewName); + if (!source_fv) { + return; + } + links.push({ + source: { + type: FEAST_FCO_TYPES["dataSource"], + name: source_fv?.spec.batchSource.name || '', + }, + target: { + type: FEAST_FCO_TYPES["featureView"], + name: fv.spec.name, + }, + }); + } + }); }); return links; diff --git a/ui/src/utils/custom-tabs/StreamFeatureViewCustomTabLoadingWrapper.tsx b/ui/src/utils/custom-tabs/StreamFeatureViewCustomTabLoadingWrapper.tsx new file mode 100644 index 00000000000..8f311b60a76 --- /dev/null +++ b/ui/src/utils/custom-tabs/StreamFeatureViewCustomTabLoadingWrapper.tsx @@ -0,0 +1,46 @@ +import React from "react"; + +import { useParams } from "react-router-dom"; +import useLoadFeatureView from "../../pages/feature-views/useLoadFeatureView"; +import { + StreamFeatureViewCustomTabProps, + StreamFeatureViewQueryReturnType, +} from "../../custom-tabs/types"; +import { FEAST_FV_TYPES } from "../../parsers/mergedFVTypes"; + +interface StreamFeatureViewCustomTabLoadingWrapperProps { + Component: (props: StreamFeatureViewCustomTabProps) => JSX.Element; +} + +const StreamFeatureViewCustomTabLoadingWrapper = ({ + Component, +}: StreamFeatureViewCustomTabLoadingWrapperProps) => { + const { featureViewName } = useParams(); + + if (!featureViewName) { + throw new Error( + `This route has no 'featureViewName' part. This route is likely not supposed to render this component.` + ); + } + + const feastObjectQuery = useLoadFeatureView(featureViewName); + + if ( + feastObjectQuery.isSuccess && + feastObjectQuery.data && + feastObjectQuery.data.type !== FEAST_FV_TYPES.stream + ) { + throw new Error( + `This should not happen. Somehow a custom tab on a ODFV page received data that does not have the shape?` + ); + } + + return ( + + ); +}; + +export default StreamFeatureViewCustomTabLoadingWrapper; From 5fb4833a4c94f0f27d3c4df9421c6a74ac240d45 Mon Sep 17 00:00:00 2001 From: hao-affirm <104030690+hao-affirm@users.noreply.github.com> Date: Tue, 27 Sep 2022 12:07:27 -0700 Subject: [PATCH 2/8] update source Signed-off-by: hao-affirm <104030690+hao-affirm@users.noreply.github.com> --- ui/src/parsers/feastSFVS.ts | 41 +++++-------------------------------- 1 file changed, 5 insertions(+), 36 deletions(-) diff --git a/ui/src/parsers/feastSFVS.ts b/ui/src/parsers/feastSFVS.ts index 29d01737530..a8cea0c3ea3 100644 --- a/ui/src/parsers/feastSFVS.ts +++ b/ui/src/parsers/feastSFVS.ts @@ -8,18 +8,8 @@ const FeatureViewProjectionSchema = z.object({ }), }); -const RequestDataSourceSchema = z.object({ - requestDataSource: z.object({ - type: z.string(), - name: z.string(), - requestDataOptions: z.object({ - schema: z.array(FeastFeatureColumnSchema), - }), - }), -}); - -const StreamDataSourceSchema = z.object({ - streamDataSource: z.object({ +const StreamSourceSchema = z.object({ + streamSource: z.object({ type: z.string(), name: z.string(), streamDataOptions: z.object({ @@ -28,32 +18,11 @@ const StreamDataSourceSchema = z.object({ }), }); -const ODFVInputsSchema = z.union([ - FeatureViewProjectionSchema, - StreamDataSourceSchema, -]); - const SFVInputsSchema = z.union([ FeatureViewProjectionSchema, - StreamDataSourceSchema, + StreamSourceSchema, ]); -const FeastODFVSchema = z.object({ - spec: z.object({ - name: z.string(), - features: z.array(FeastFeatureColumnSchema), - sources: z.record(ODFVInputsSchema), - userDefinedFunction: z.object({ - name: z.string(), - body: z.string(), - }), - }), - meta: z.object({ - createdTimestamp: z.string().transform((val) => new Date(val)), - lastUpdatedTimestamp: z.string().transform((val) => new Date(val)), - }), -}); - const FeastSFVSchema = z.object({ spec: z.object({ name: z.string(), @@ -71,7 +40,7 @@ const FeastSFVSchema = z.object({ }); type FeastSFVType = z.infer; -type StreamDataSourceType = z.infer; +type StreamSourceType = z.infer; export { FeastSFVSchema }; -export type { FeastSFVType, StreamDataSourceType}; +export type { FeastSFVType, StreamSourceType}; From 32384f3f544e5a2e16487616d38c4f33ef6f8adc Mon Sep 17 00:00:00 2001 From: hao-affirm <104030690+hao-affirm@users.noreply.github.com> Date: Tue, 27 Sep 2022 13:28:56 -0700 Subject: [PATCH 3/8] update example Signed-off-by: hao-affirm <104030690+hao-affirm@users.noreply.github.com> --- ui/public/registry.json | 72 ++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 44 deletions(-) diff --git a/ui/public/registry.json b/ui/public/registry.json index 1c5d2cf4d7e..2b296b99d11 100644 --- a/ui/public/registry.json +++ b/ui/public/registry.json @@ -637,58 +637,42 @@ "lastUpdatedTimestamp": "2022-05-11T19:27:03.171556Z" }, "spec": { + "batchSource": { + "createdTimestampColumn": "created_timestamp", + "dataSourceClassType": "feast.infra.offline_stores.file_source.FileSource", + "fileOptions": { + "uri": "data/zipcode_table.parquet" + }, + "name": "zipcode", + "timestampField": "event_timestamp", + "type": "BATCH_FILE" + }, "features": [ { "name": "transaction_gt_last_credit_card_due", "valueType": "BOOL" } ], - "name": "transaction_gt_last_credit_card_due", - "sources": { - "credit_history": { - "featureViewProjection": { - "featureColumns": [ - { - "name": "credit_card_due", - "valueType": "INT64" - }, - { - "name": "mortgage_due", - "valueType": "INT64" - }, - { - "name": "student_loan_due", - "valueType": "INT64" - }, - { - "name": "vehicle_loan_due", - "valueType": "INT64" - }, - { - "name": "hard_pulls", - "valueType": "INT64" - }, - { - "name": "missed_payments_2y", - "valueType": "INT64" - }, - { - "name": "missed_payments_1y", - "valueType": "INT64" - }, - { - "name": "missed_payments_6m", - "valueType": "INT64" - }, - { - "name": "bankruptcies", - "valueType": "INT64" - } - ], - "featureViewName": "credit_history" - } + "name": "transaction_stream_example", + "streamSource": { + "batchSource": { + "fileOptions": { + "uri": "data/zipcode_table.parquet" + }, + "name": "user_stats", + "timestampField": "timestamp", + "type": "BATCH_FILE" }, + "dataSourceClassType": "feast.data_source.KafkaSource", + "description": "The Kafka stream example", + "kafkaOptions": {"messageFormat": {"jsonFormat": {"schemaJson": "id string, timestamp timestamp"}}, + "watermarkDelayThreshold": "300s"}, + "name": "kafka_stream_example", + "owner": "test@gmail.com", + "timestampField": "timestamp", + "type": "STREAM_KAFKA" }, + "ttl": "86400s", "userDefinedFunction": { "body": "@on_demand_feature_view(\n sources=[credit_history, input_request],\n schema=[\n Field(name=\"transaction_gt_last_credit_card_due\", dtype=Bool),\n ],\n)\ndef transaction_gt_last_credit_card_due(inputs: pd.DataFrame) -> pd.DataFrame:\n df = pd.DataFrame()\n df[\"transaction_gt_last_credit_card_due\"] = (\n inputs[\"transaction_amt\"] > inputs[\"credit_card_due\"]\n )\n return df\n", "name": "transaction_gt_last_credit_card_due" From a0ef7488e2765bdb44603144e3a39fcf32cf788d Mon Sep 17 00:00:00 2001 From: hao-affirm <104030690+hao-affirm@users.noreply.github.com> Date: Tue, 27 Sep 2022 15:25:13 -0700 Subject: [PATCH 4/8] add registry Signed-off-by: hao-affirm <104030690+hao-affirm@users.noreply.github.com> --- ui/public/registry.json | 32 +++++++++++--- .../StreamFeatureViewInstance.tsx | 4 +- .../StreamFeatureViewOverviewTab.tsx | 40 +++++++++-------- ui/src/parsers/feastSFVS.ts | 19 +++----- ui/src/parsers/parseEntityRelationships.ts | 43 ++++++------------- ui/src/queries/useLoadRegistry.ts | 1 + 6 files changed, 72 insertions(+), 67 deletions(-) diff --git a/ui/public/registry.json b/ui/public/registry.json index 2b296b99d11..279c9d08327 100644 --- a/ui/public/registry.json +++ b/ui/public/registry.json @@ -29,6 +29,24 @@ "name": "zipcode", "timestampField": "event_timestamp", "type": "BATCH_FILE" + }, + { + "batchSource": { + "fileOptions": { + "uri": "data/zipcode_table.parquet" + }, + "name": "user_stats", + "timestampField": "timestamp", + "type": "BATCH_FILE" + }, + "dataSourceClassType": "feast.data_source.KafkaSource", + "description": "The Kafka stream example", + "kafkaOptions": {"messageFormat": {"jsonFormat": {"schemaJson": "id string, timestamp timestamp"}}, + "watermarkDelayThreshold": "300s"}, + "name": "driver_stats_stream", + "owner": "test@gmail.com", + "timestampField": "timestamp", + "type": "STREAM_KAFKA" } ], "entities": [ @@ -649,8 +667,12 @@ }, "features": [ { - "name": "transaction_gt_last_credit_card_due", - "valueType": "BOOL" + "name": "conv_percentage", + "valueType": "FLOAT" + }, + { + "name": "acc_percentage", + "valueType": "FLOAT" } ], "name": "transaction_stream_example", @@ -667,15 +689,15 @@ "description": "The Kafka stream example", "kafkaOptions": {"messageFormat": {"jsonFormat": {"schemaJson": "id string, timestamp timestamp"}}, "watermarkDelayThreshold": "300s"}, - "name": "kafka_stream_example", + "name": "driver_stats_stream", "owner": "test@gmail.com", "timestampField": "timestamp", "type": "STREAM_KAFKA" }, "ttl": "86400s", "userDefinedFunction": { - "body": "@on_demand_feature_view(\n sources=[credit_history, input_request],\n schema=[\n Field(name=\"transaction_gt_last_credit_card_due\", dtype=Bool),\n ],\n)\ndef transaction_gt_last_credit_card_due(inputs: pd.DataFrame) -> pd.DataFrame:\n df = pd.DataFrame()\n df[\"transaction_gt_last_credit_card_due\"] = (\n inputs[\"transaction_amt\"] > inputs[\"credit_card_due\"]\n )\n return df\n", - "name": "transaction_gt_last_credit_card_due" + "body": "@stream_feature_view(\n sources=[driver_stats_stream_source],\n mode=\"spark\",\n schema=[\n Field(name=\"conv_percentage\", dtype=Float32),\n Field(name=\"acc_percentage\", dtype=Float32),\n ],\n timestamp_field=\"event_timestamp\",\n online=True,\n source=driver_stats_stream_source,\n tags={},\n)\ndef driver_hourly_stats_stream(df: DataFrame) -> DataFrame:\n from pyspark.sql.functions import col\n return (\n df.withColumn(\"conv_percentage\", col(\"conv_rate\") * 100.0)\n .withColumn(\"acc_percentage\", col(\"acc_rate\") * 100.0)\n .drop(\"conv_rate\", \"acc_rate\")\n )\n", + "name": "driver_hourly_stats_stream" } } } diff --git a/ui/src/pages/feature-views/StreamFeatureViewInstance.tsx b/ui/src/pages/feature-views/StreamFeatureViewInstance.tsx index 404134fee04..ba4c0087278 100644 --- a/ui/src/pages/feature-views/StreamFeatureViewInstance.tsx +++ b/ui/src/pages/feature-views/StreamFeatureViewInstance.tsx @@ -9,7 +9,7 @@ import { import { FeatureViewIcon32 } from "../../graphics/FeatureViewIcon"; import { useMatchExact } from "../../hooks/useMatchSubpath"; -import { FeastODFVType } from "../../parsers/feastODFVS"; +import { FeastSFVType } from "../../parsers/feastSFVS"; import StreamFeatureViewOverviewTab from "./StreamFeatureViewOverviewTab"; import { @@ -18,7 +18,7 @@ import { } from "../../custom-tabs/TabsRegistryContext"; interface StreamFeatureInstanceProps { - data: FeastODFVType; + data: FeastSFVType; } const StreamFeatureInstance = ({ data }: StreamFeatureInstanceProps) => { diff --git a/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx b/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx index 4b891331424..9b131180167 100644 --- a/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx +++ b/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx @@ -11,18 +11,19 @@ import { import React from "react"; import FeaturesListDisplay from "../../components/FeaturesListDisplay"; import { - FeastODFVType, + FeastSFVType, FeatureViewProjectionType, -} from "../../parsers/feastODFVS"; +} from "../../parsers/feastSFVS"; import { useParams } from "react-router-dom"; import { EntityRelation } from "../../parsers/parseEntityRelationships"; import { FEAST_FCO_TYPES } from "../../parsers/types"; import useLoadRelationshipData from "../../queries/useLoadRelationshipsData"; import FeatureViewProjectionDisplayPanel from "./components/FeatureViewProjectionDisplayPanel"; import ConsumingFeatureServicesList from "./ConsumingFeatureServicesList"; +import EuiCustomLink from "../../components/EuiCustomLink"; interface StreamFeatureViewOverviewTabProps { - data: FeastODFVType; + data: FeastSFVType; } const whereFSconsumesThisFv = (fvName: string) => { @@ -37,7 +38,7 @@ const whereFSconsumesThisFv = (fvName: string) => { const StreamFeatureViewOverviewTab = ({ data, }: StreamFeatureViewOverviewTabProps) => { - const inputs = Object.entries(data.spec.sources); + const inputs = Object.entries([data.spec.streamSource]); const { projectName } = useParams(); const relationshipQuery = useLoadRelationshipData(); @@ -92,22 +93,25 @@ const StreamFeatureViewOverviewTab = ({ {inputs.map(([key, inputGroup]) => { - if (inputGroup as FeatureViewProjectionType) { - return ( + return ( + + + Stream Source + + + + {inputGroup.name} + + - + + {JSON.stringify(inputGroup, null, 2)} + - ); - } - - return ( - - - {JSON.stringify(inputGroup, null, 2)} - - + ); })} diff --git a/ui/src/parsers/feastSFVS.ts b/ui/src/parsers/feastSFVS.ts index a8cea0c3ea3..f21b3d1cdac 100644 --- a/ui/src/parsers/feastSFVS.ts +++ b/ui/src/parsers/feastSFVS.ts @@ -1,5 +1,6 @@ import { z } from "zod"; import { FeastFeatureColumnSchema } from "./feastFeatureViews"; +import {FeastDatasourceSchema} from "./feastDatasources"; const FeatureViewProjectionSchema = z.object({ featureViewProjection: z.object({ @@ -9,25 +10,18 @@ const FeatureViewProjectionSchema = z.object({ }); const StreamSourceSchema = z.object({ - streamSource: z.object({ type: z.string(), name: z.string(), - streamDataOptions: z.object({ - schema: z.array(FeastFeatureColumnSchema), - }), - }), + owner: z.string().optional(), + description: z.string().optional(), }); -const SFVInputsSchema = z.union([ - FeatureViewProjectionSchema, - StreamSourceSchema, -]); - const FeastSFVSchema = z.object({ spec: z.object({ name: z.string(), features: z.array(FeastFeatureColumnSchema), - sources: z.record(SFVInputsSchema), + batchSource: FeastDatasourceSchema, + streamSource: StreamSourceSchema, userDefinedFunction: z.object({ name: z.string(), body: z.string(), @@ -41,6 +35,7 @@ const FeastSFVSchema = z.object({ type FeastSFVType = z.infer; type StreamSourceType = z.infer; +type FeatureViewProjectionType = z.infer; export { FeastSFVSchema }; -export type { FeastSFVType, StreamSourceType}; +export type { FeastSFVType, StreamSourceType, FeatureViewProjectionType}; diff --git a/ui/src/parsers/parseEntityRelationships.ts b/ui/src/parsers/parseEntityRelationships.ts index 0cf9ea783e5..2e1f49a235a 100644 --- a/ui/src/parsers/parseEntityRelationships.ts +++ b/ui/src/parsers/parseEntityRelationships.ts @@ -86,36 +86,19 @@ const parseEntityRelationships = (objects: FeastRegistryType) => { }); } }); - objects.streamFeatureViews?.forEach((fv) => { - Object.values(fv.spec.sources).forEach((input: { [key: string]: any }) => { - if (input.requestDataSource) { - links.push({ - source: { - type: FEAST_FCO_TYPES["dataSource"], - name: input.streamSource.name, - }, - target: { - type: FEAST_FCO_TYPES["featureView"], - name: fv.spec.name, - }, - }); - } else if (input.featureViewProjection?.featureViewName) { - const source_fv = objects.featureViews?.find(el => el.spec.name === input.featureViewProjection.featureViewName); - if (!source_fv) { - return; - } - links.push({ - source: { - type: FEAST_FCO_TYPES["dataSource"], - name: source_fv?.spec.batchSource.name || '', - }, - target: { - type: FEAST_FCO_TYPES["featureView"], - name: fv.spec.name, - }, - }); - } - }); + }); + + objects.streamFeatureViews?.forEach((fv) => { + links.push({ + source: { + type: FEAST_FCO_TYPES["dataSource"], + name: fv.spec.streamSource.name, + }, + target: { + type: FEAST_FCO_TYPES["featureView"], + name: fv.spec.name, + }, + }); }); return links; diff --git a/ui/src/queries/useLoadRegistry.ts b/ui/src/queries/useLoadRegistry.ts index ffb06756437..01781f5f462 100644 --- a/ui/src/queries/useLoadRegistry.ts +++ b/ui/src/queries/useLoadRegistry.ts @@ -32,6 +32,7 @@ const useLoadRegistry = (url: string) => { return res.json(); }) .then((json) => { + console.log(json) const objects = FeastRegistrySchema.parse(json); const { mergedFVMap, mergedFVList } = mergedFVTypes(objects); From d91be15d7780f85ac082cb9a574de1318244b37f Mon Sep 17 00:00:00 2001 From: hao-affirm <104030690+hao-affirm@users.noreply.github.com> Date: Tue, 27 Sep 2022 15:36:35 -0700 Subject: [PATCH 5/8] fix lint Signed-off-by: hao-affirm <104030690+hao-affirm@users.noreply.github.com> --- ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx b/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx index 9b131180167..223ea709997 100644 --- a/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx +++ b/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx @@ -12,13 +12,11 @@ import React from "react"; import FeaturesListDisplay from "../../components/FeaturesListDisplay"; import { FeastSFVType, - FeatureViewProjectionType, } from "../../parsers/feastSFVS"; import { useParams } from "react-router-dom"; import { EntityRelation } from "../../parsers/parseEntityRelationships"; import { FEAST_FCO_TYPES } from "../../parsers/types"; import useLoadRelationshipData from "../../queries/useLoadRelationshipsData"; -import FeatureViewProjectionDisplayPanel from "./components/FeatureViewProjectionDisplayPanel"; import ConsumingFeatureServicesList from "./ConsumingFeatureServicesList"; import EuiCustomLink from "../../components/EuiCustomLink"; From fb137cc0ac694c16703d219f17cc76085c40b179 Mon Sep 17 00:00:00 2001 From: hao-affirm <104030690+hao-affirm@users.noreply.github.com> Date: Wed, 28 Sep 2022 15:24:41 -0700 Subject: [PATCH 6/8] fix bug Signed-off-by: hao-affirm <104030690+hao-affirm@users.noreply.github.com> --- ui/src/queries/useLoadRegistry.ts | 1 - .../custom-tabs/StreamFeatureViewCustomTabLoadingWrapper.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/src/queries/useLoadRegistry.ts b/ui/src/queries/useLoadRegistry.ts index 01781f5f462..ffb06756437 100644 --- a/ui/src/queries/useLoadRegistry.ts +++ b/ui/src/queries/useLoadRegistry.ts @@ -32,7 +32,6 @@ const useLoadRegistry = (url: string) => { return res.json(); }) .then((json) => { - console.log(json) const objects = FeastRegistrySchema.parse(json); const { mergedFVMap, mergedFVList } = mergedFVTypes(objects); diff --git a/ui/src/utils/custom-tabs/StreamFeatureViewCustomTabLoadingWrapper.tsx b/ui/src/utils/custom-tabs/StreamFeatureViewCustomTabLoadingWrapper.tsx index 8f311b60a76..098ab848a55 100644 --- a/ui/src/utils/custom-tabs/StreamFeatureViewCustomTabLoadingWrapper.tsx +++ b/ui/src/utils/custom-tabs/StreamFeatureViewCustomTabLoadingWrapper.tsx @@ -31,7 +31,7 @@ const StreamFeatureViewCustomTabLoadingWrapper = ({ feastObjectQuery.data.type !== FEAST_FV_TYPES.stream ) { throw new Error( - `This should not happen. Somehow a custom tab on a ODFV page received data that does not have the shape?` + `This should not happen. Somehow a custom tab on a SFV page received data that does not have the shape?` ); } From 9478b10eb4c3ad21bb016e0723c3ba2c409b9f45 Mon Sep 17 00:00:00 2001 From: hao-affirm <104030690+hao-affirm@users.noreply.github.com> Date: Thu, 29 Sep 2022 00:41:02 -0700 Subject: [PATCH 7/8] add batch source Signed-off-by: hao-affirm <104030690+hao-affirm@users.noreply.github.com> --- ui/src/parsers/parseEntityRelationships.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ui/src/parsers/parseEntityRelationships.ts b/ui/src/parsers/parseEntityRelationships.ts index 2e1f49a235a..8424bb7a44f 100644 --- a/ui/src/parsers/parseEntityRelationships.ts +++ b/ui/src/parsers/parseEntityRelationships.ts @@ -89,6 +89,7 @@ const parseEntityRelationships = (objects: FeastRegistryType) => { }); objects.streamFeatureViews?.forEach((fv) => { + // stream source links.push({ source: { type: FEAST_FCO_TYPES["dataSource"], @@ -99,6 +100,18 @@ const parseEntityRelationships = (objects: FeastRegistryType) => { name: fv.spec.name, }, }); + + // batch source + links.push({ + source: { + type: FEAST_FCO_TYPES["dataSource"], + name: fv.spec.batchSource.name, + }, + target: { + type: FEAST_FCO_TYPES["featureView"], + name: fv.spec.name, + }, + }); }); return links; From ab2d8c890acb3e35006f564f4a67ad8cec03c2a9 Mon Sep 17 00:00:00 2001 From: hao-affirm <104030690+hao-affirm@users.noreply.github.com> Date: Thu, 29 Sep 2022 00:44:44 -0700 Subject: [PATCH 8/8] fix warning Signed-off-by: hao-affirm <104030690+hao-affirm@users.noreply.github.com> --- ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx b/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx index 223ea709997..56efc428453 100644 --- a/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx +++ b/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx @@ -92,7 +92,7 @@ const StreamFeatureViewOverviewTab = ({ {inputs.map(([key, inputGroup]) => { return ( - + Stream Source