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==