Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Add metadata tab functionality
Signed-off-by: Daniel Kim <danielk@twitter.com>
  • Loading branch information
Daniel Kim committed Jul 1, 2022
commit c084f9cd4661f91ec57314fab2b7ffa1a2e5f41e
157 changes: 91 additions & 66 deletions ui/src/custom-tabs/reguar-fv-demo-tab/DemoCustomTab.tsx
Original file line number Diff line number Diff line change
@@ -1,83 +1,108 @@
import React from "react";

import {
// Feature View Custom Tabs will get these props
RegularFeatureViewCustomTabProps,
} from "../types";

import { z } from "zod";
import {
EuiLoadingContent,
EuiEmptyPrompt,
EuiCode,
EuiFlexGroup,
EuiHorizontalRule,
EuiLoadingSpinner,
EuiTable,
EuiTitle,
EuiTableHeader,
EuiTableHeaderCell,
EuiPanel,
EuiFlexItem,
EuiCode,
EuiSpacer,
EuiTableRow,
EuiTableRowCell,
} from "@elastic/eui";
import useLoadRegularFeatureView from "../../pages/feature-views/useLoadFeatureView";

// Separating out the query is not required,
// but encouraged for code readability
import useDemoQuery from "./useDemoQuery";
const FeatureViewMetadataRow = z.object({
name: z.string(),
value: z.string(),
});

const DemoCustomTab = ({
id,
feastObjectQuery,
}: RegularFeatureViewCustomTabProps) => {
// 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,
});
type FeatureViewMetadataRowType = z.infer<typeof FeatureViewMetadataRow>;

if (isLoading) {
// Handle Loading State
// https://elastic.github.io/eui/#/display/loading
return <EuiLoadingContent lines={3} />;
}
const LineHeightProp: React.CSSProperties = {
lineHeight: 1,
}

if (isError) {
// Handle Data Fetching Error
// https://elastic.github.io/eui/#/display/empty-prompt
return (
<EuiEmptyPrompt
iconType="alert"
color="danger"
title={<h2>Unable to load your demo page</h2>}
body={
<p>
There was an error loading the Dashboard application. Contact your
administrator for help.
</p>
}
/>
);
const EuiFeatureViewMetadataRow = ({name, value}: FeatureViewMetadataRowType) => {
return (
<EuiTableRow>
<EuiTableRowCell>
{name}
</EuiTableRowCell>
<EuiTableRowCell textOnly={false}>
<EuiCode data-code-language="text">
<pre style={LineHeightProp}>
{value}
</pre>
</EuiCode>
</EuiTableRowCell>
</EuiTableRow>
);
}

const FeatureViewMetadataTable = (data: any) => {
var items: FeatureViewMetadataRowType[] = [];

for (let element in data.data){
const row: FeatureViewMetadataRowType = {
name: element,
value: JSON.stringify(data.data[element], null, 2),
};
items.push(row);
console.log(row);
}

// Feast UI uses the Elastic UI component system.
// <EuiFlexGroup> and <EuiFlexItem> are particularly
// useful for layouts.
return (
<EuiTable>
<EuiTableHeader>
<EuiTableHeaderCell>
Metadata Feature Name
</EuiTableHeaderCell>
<EuiTableHeaderCell>
Metadata Feature Value
</EuiTableHeaderCell>
</EuiTableHeader>
{items.map((item) => {
return <EuiFeatureViewMetadataRow name={item.name} value={item.value} />
})}
</EuiTable>
)
}

// TODO: change this part to load from a custom source, like the demos?
const DemoCustomTab = () => {
const fName = "credit_history"
const { isLoading, isError, isSuccess, data } = useLoadRegularFeatureView(fName);
const isEmpty = data === undefined;

return (
<React.Fragment>
{isLoading && (
<React.Fragment>
<EuiLoadingSpinner size="m" /> Loading
</React.Fragment>
)}
{isEmpty && <p>No feature view with name: {fName}</p>}
{isError && <p>Error loading feature view: {fName}</p>}
{isSuccess && data && (
<React.Fragment>
<EuiFlexGroup>
<EuiFlexItem grow={1}>
<p>Hello World. The following is fetched data.</p>
<EuiSpacer />
{isSuccess && data && (
<EuiCode>
<pre>{JSON.stringify(data, null, 2)}</pre>
</EuiCode>
)}
</EuiFlexItem>
<EuiFlexItem grow={2}>
<p>... and this is data from Feast UI&rsquo;s own query.</p>
<EuiSpacer />
{feastObjectQuery.isSuccess && feastObjectQuery.data && (
<EuiCode>
<pre>{JSON.stringify(feastObjectQuery.data, null, 2)}</pre>
</EuiCode>
)}
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexItem>
<EuiPanel hasBorder={true}>
<EuiTitle size="xs">
<h3>Properties</h3>
</EuiTitle>
<EuiHorizontalRule margin="xs" />
<FeatureViewMetadataTable data={data.object.spec} />
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</React.Fragment>
)}
</React.Fragment>
);
};
Expand Down
44 changes: 44 additions & 0 deletions ui/src/custom-tabs/reguar-fv-demo-tab/useCustomQuery.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof demoSchema>;

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<DemoDataType>(
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 };
3 changes: 3 additions & 0 deletions ui/src/parsers/feastFeatureViews.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { z } from "zod";
import { FEAST_FEATURE_VALUE_TYPES } from "./types";
import { jsonSchema } from "./jsonType"


const FeastFeatureColumnSchema = z.object({
name: z.string(),
Expand Down Expand Up @@ -54,6 +56,7 @@ const FeastFeatureViewSchema = z.object({
})
)
.optional(),
metadata: jsonSchema.optional(),
}),
});

Expand Down
11 changes: 11 additions & 0 deletions ui/src/parsers/jsonType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { z } from "zod";

// Taken from the zod documentation code - accepts any JSON object.
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
type Literal = z.infer<typeof literalSchema>;
type Json = Literal | { [key: string]: Json } | Json[];
const jsonSchema: z.ZodType<Json> = z.lazy(() =>
z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)])
);

export { jsonSchema };