diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml
index de6d98d1400..ebc09f0080e 100644
--- a/.github/workflows/unit_tests.yml
+++ b/.github/workflows/unit_tests.yml
@@ -108,3 +108,23 @@ jobs:
sudo apt install -y -V libarrow-dev
- name: Test
run: make test-go
+
+ unit-test-ui:
+ runs-on: ubuntu-latest
+ env:
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v2
+ with:
+ node-version: '17.x'
+ registry-url: 'https://registry.npmjs.org'
+ - name: Install yarn dependencies
+ working-directory: ./ui
+ run: yarn install
+ - name: Build yarn rollup
+ working-directory: ./ui
+ run: yarn build:lib
+ - name: Run yarn tests
+ working-directory: ./ui
+ run: yarn test --watchAll=false
diff --git a/sdk/python/feast/ui/README.md b/sdk/python/feast/ui/README.md
index 220f40a2251..28290f0326a 100644
--- a/sdk/python/feast/ui/README.md
+++ b/sdk/python/feast/ui/README.md
@@ -25,4 +25,17 @@ The `feast ui` command will generate the necessary `projects-list.json` file and
**Note**: `yarn start` will not work on this because of the above dependency.
## Dev
-To test, do `yarn link` in ui/ and then come here to do `yarn link @feast-dev/feast`
\ No newline at end of file
+To test with a locally built Feast UI package, do:
+1. `yarn link` in ui/
+2. `yarn install` in ui/
+3. `yarn link` in ui/node_modules/react
+4. `yarn link` in ui/node_modules/react-dom
+5. and then come here to do:
+ ```bash
+ yarn link "@feast-dev/feast"
+ yarn link react
+ yarn link react-dom
+ yarn start
+ ```
+
+See also https://github.com/facebook/react/issues/14257.
\ No newline at end of file
diff --git a/ui/package.json b/ui/package.json
index 7d50b3e086b..64952897a29 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -25,7 +25,8 @@
"react-router-dom": "6",
"react-scripts": "^5.0.0",
"typescript": "^4.4.2",
- "use-query-params": "^1.2.3"
+ "use-query-params": "^1.2.3",
+ "zod": "^3.11.6"
},
"dependencies": {
"@elastic/datemath": "^5.0.3",
@@ -45,7 +46,8 @@
"react-query": "^3.34.12",
"react-router-dom": "6",
"react-scripts": "^5.0.0",
- "use-query-params": "^1.2.3"
+ "use-query-params": "^1.2.3",
+ "zod": "^3.11.6"
},
"scripts": {
"start": "npm run generate-protos && react-scripts start",
diff --git a/ui/src/FeastUISansProviders.test.tsx b/ui/src/FeastUISansProviders.test.tsx
index 09985bc1338..46702328090 100644
--- a/ui/src/FeastUISansProviders.test.tsx
+++ b/ui/src/FeastUISansProviders.test.tsx
@@ -15,13 +15,17 @@ import {
creditHistoryRegistry,
} from "./mocks/handlers";
-import registry from "../public/registry.json";
+import { readFileSync } from "fs";
+import { feast } from "./protos";
+import path from "path";
// declare which API requests to mock
const server = setupServer(
projectsListWithDefaultProject,
creditHistoryRegistry
);
+const registry = readFileSync(path.resolve(__dirname, "../public/registry.db"));
+const parsedRegistry = feast.core.Registry.decode(registry);
// establish API mocking before all tests
beforeAll(() => server.listen());
@@ -50,7 +54,10 @@ test("full app rendering", async () => {
// Explore Panel Should Appear
expect(screen.getByText(/Explore this Project/i)).toBeInTheDocument();
- const projectNameRegExp = new RegExp(registry.project, "i");
+ const projectNameRegExp = new RegExp(
+ parsedRegistry.projectMetadata[0].project!,
+ "i"
+ );
// It should load the default project, which is credit_scoring_aws
await waitFor(() => {
@@ -95,9 +102,9 @@ test("routes are reachable", async () => {
}
});
-
-const featureViewName = registry.featureViews[0].spec.name;
-const featureName = registry.featureViews[0].spec.features[0].name;
+const spec = parsedRegistry.featureViews[0].spec!;
+const featureViewName = spec.name!;
+const featureName = spec.features![0]!.name!;
test("features are reachable", async () => {
render();
@@ -106,10 +113,7 @@ test("features are reachable", async () => {
await screen.findByText(/Explore this Project/i);
const routeRegExp = new RegExp("Feature Views", "i");
- userEvent.click(
- screen.getByRole("button", { name: routeRegExp }),
- leftClick
- );
+ userEvent.click(screen.getByRole("button", { name: routeRegExp }), leftClick);
screen.getByRole("heading", {
name: "Feature Views",
@@ -118,18 +122,12 @@ test("features are reachable", async () => {
await screen.findAllByText(/Feature Views/i);
const fvRegExp = new RegExp(featureViewName, "i");
- userEvent.click(
- screen.getByRole("link", { name: fvRegExp }),
- leftClick
- )
+ userEvent.click(screen.getByRole("link", { name: fvRegExp }), leftClick);
await screen.findByText(featureName);
const fRegExp = new RegExp(featureName, "i");
- userEvent.click(
- screen.getByRole("link", { name: fRegExp }),
- leftClick
- )
+ userEvent.click(screen.getByRole("link", { name: fRegExp }), leftClick);
// Should land on a page with the heading
// await screen.findByText("Feature: " + featureName);
screen.getByRole("heading", {
diff --git a/ui/src/mocks/handlers.ts b/ui/src/mocks/handlers.ts
index e7b0040f0dc..39f30b62a6d 100644
--- a/ui/src/mocks/handlers.ts
+++ b/ui/src/mocks/handlers.ts
@@ -1,5 +1,8 @@
import { rest } from "msw";
-import registry from "../../public/registry.json";
+import {readFileSync} from 'fs';
+import path from "path";
+
+const registry = readFileSync(path.resolve(__dirname, "../../public/registry.db"));
const projectsListWithDefaultProject = rest.get(
"/projects-list.json",
@@ -14,7 +17,7 @@ const projectsListWithDefaultProject = rest.get(
description:
"Project for credit scoring team and associated models.",
id: "credit_score_project",
- registryPath: "/registry.json",
+ registryPath: "/registry.pb",
},
],
})
@@ -22,8 +25,11 @@ const projectsListWithDefaultProject = rest.get(
}
);
-const creditHistoryRegistry = rest.get("/registry.json", (req, res, ctx) => {
- return res(ctx.status(200), ctx.json(registry));
+const creditHistoryRegistry = rest.get("/registry.pb", (req, res, ctx) => {
+ return res(
+ ctx.status(200),
+ ctx.set('Content-Type', 'application/octet-stream'),
+ ctx.body(registry));
});
export { projectsListWithDefaultProject, creditHistoryRegistry };
diff --git a/ui/src/parsers/jsonType.ts b/ui/src/parsers/jsonType.ts
deleted file mode 100644
index be484b5477f..00000000000
--- a/ui/src/parsers/jsonType.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-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;
-type Json = Literal | { [key: string]: Json } | Json[];
-const jsonSchema: z.ZodType = z.lazy(() =>
- z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)])
-);
-
-export { jsonSchema };
diff --git a/ui/yarn.lock b/ui/yarn.lock
index e056cad6179..948eb78796e 100644
--- a/ui/yarn.lock
+++ b/ui/yarn.lock
@@ -3559,9 +3559,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001297, caniuse-lite@^1.0.30001299:
- version "1.0.30001303"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001303.tgz#9b168e4f43ccfc372b86f4bc5a551d9b909c95c9"
- integrity sha512-/Mqc1oESndUNszJP0kx0UaQU9kEv9nNtJ7Kn8AdA0mNnH8eR1cj0kG+NbNuC1Wq/b21eA8prhKRA3bbkjONegQ==
+ version "1.0.30001416"
+ resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001416.tgz"
+ integrity sha512-06wzzdAkCPZO+Qm4e/eNghZBDfVNDsCgw33T27OwBH9unE9S478OYw//Q2L7Npf/zBzs7rjZOszIFQkwQKAEqA==
case-sensitive-paths-webpack-plugin@^2.4.0:
version "2.4.0"
@@ -11342,7 +11342,7 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
-zod@^3.19.1:
+zod@^3.11.6:
version "3.19.1"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.19.1.tgz#112f074a97b50bfc4772d4ad1576814bd8ac4473"
integrity sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA==