diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9587044a84d..039ab6a24e7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -46,7 +46,7 @@ jobs: build-publish-docker-images: runs-on: ubuntu-latest - needs: get-version + needs: [get-version, publish-python-sdk] strategy: matrix: component: [feature-server, feature-server-python-aws, feature-server-java, feature-transformation-server] 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/.releaserc.js b/.releaserc.js index 124ad8801c2..114d65d1a2a 100644 --- a/.releaserc.js +++ b/.releaserc.js @@ -67,6 +67,7 @@ module.exports = { "java/pom.xml", "infra/charts/**/*.*", "ui/package.json", + "sdk/python/feast/ui/package.json", "sdk/python/feast/ui/yarn.lock" ], message: "chore(release): release ${nextRelease.version}\n\n${nextRelease.notes}" diff --git a/CHANGELOG.md b/CHANGELOG.md index 41512e4a49a..7a8fd81055b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [0.25.2](https://github.com/feast-dev/feast/compare/v0.25.1...v0.25.2) (2022-10-06) + + +### Bug Fixes + +* Fix docker image for feature-server ([#3272](https://github.com/feast-dev/feast/issues/3272)) ([4ce366a](https://github.com/feast-dev/feast/commit/4ce366a6d3320b7f7862d69fc1d44fde46566a55)) +* Fix Feast UI release process to update the feast-ui package ([#3267](https://github.com/feast-dev/feast/issues/3267)) ([d118fe4](https://github.com/feast-dev/feast/commit/d118fe43756e16fbe912a31ed121be7c8e15b9da)) +* Stream feature view meta undefined created_timestamp issue ([#3266](https://github.com/feast-dev/feast/issues/3266)) ([efbd4b0](https://github.com/feast-dev/feast/commit/efbd4b040deb96eef86eb45cd420bfe5b64ffa96)) +* Udf in stream feature view UI shows pickled data ([#3268](https://github.com/feast-dev/feast/issues/3268)) ([f4a83a7](https://github.com/feast-dev/feast/commit/f4a83a7762ac0105b75b20e19822304cce97181c)) +* Updated quickstart notebook to patch an incorrect reference to an outdated featureview name ([#3271](https://github.com/feast-dev/feast/issues/3271)) ([7fa4bbf](https://github.com/feast-dev/feast/commit/7fa4bbf2be98847fa2b795fe0d7dc77d5e45b2fb)) + ## [0.25.1](https://github.com/feast-dev/feast/compare/v0.25.0...v0.25.1) (2022-09-30) diff --git a/Makefile b/Makefile index fabcb388e84..9acd2fc8622 100644 --- a/Makefile +++ b/Makefile @@ -446,4 +446,4 @@ build-helm-docs: # Note: requires node and yarn to be installed build-ui: - cd $(ROOT_DIR)/sdk/python/feast/ui && yarn install && npm run build --omit=dev + cd $(ROOT_DIR)/sdk/python/feast/ui && yarn upgrade @feast-dev/feast-ui --latest && yarn install && npm run build --omit=dev diff --git a/examples/quickstart/quickstart.ipynb b/examples/quickstart/quickstart.ipynb index 68b9d639110..cec4df91b11 100644 --- a/examples/quickstart/quickstart.ipynb +++ b/examples/quickstart/quickstart.ipynb @@ -512,7 +512,7 @@ "\n", "# This groups features into a model version\n", "driver_stats_fs = FeatureService(\n", - " name=\"driver_activity\", features=[driver_hourly_stats_view, transformed_conv_rate]\n", + " name=\"driver_activity_v1\", features=[driver_hourly_stats_view, transformed_conv_rate]\n", ")\n", "```" ] @@ -547,7 +547,7 @@ "Created entity \u001b[1m\u001b[32mdriver\u001b[0m\n", "Created feature view \u001b[1m\u001b[32mdriver_hourly_stats\u001b[0m\n", "Created on demand feature view \u001b[1m\u001b[32mtransformed_conv_rate\u001b[0m\n", - "Created feature service \u001b[1m\u001b[32mdriver_activity\u001b[0m\n", + "Created feature service \u001b[1m\u001b[32mdriver_activity_v1\u001b[0m\n", "\n", "Created sqlite table \u001b[1m\u001b[32mfeature_repo_driver_hourly_stats\u001b[0m\n", "\n" @@ -942,11 +942,11 @@ "### Fetching features using feature services\n", "You can also use feature services to manage multiple features, and decouple feature view definitions and the features needed by end applications. The feature store can also be used to fetch either online or historical features using the same api below. More information can be found [here](https://docs.feast.dev/getting-started/concepts/feature-retrieval).\n", "\n", - " The `driver_activity` feature service pulls all features from the `driver_hourly_stats` feature view:\n", + " The `driver_activity_v1` feature service pulls all features from the `driver_hourly_stats` feature view:\n", "\n", "```python\n", "driver_stats_fs = FeatureService(\n", - " name=\"driver_activity\", features=[driver_hourly_stats_view]\n", + " name=\"driver_activity_v1\", features=[driver_hourly_stats_view]\n", ")\n", "```" ] @@ -979,7 +979,7 @@ "from feast import FeatureStore\n", "feature_store = FeatureStore('.') # Initialize the feature store\n", "\n", - "feature_service = feature_store.get_feature_service(\"driver_activity\")\n", + "feature_service = feature_store.get_feature_service(\"driver_activity_v1\")\n", "feature_vector = feature_store.get_online_features(\n", " features=feature_service,\n", " entity_rows=[\n", @@ -1101,4 +1101,4 @@ }, "nbformat": 4, "nbformat_minor": 0 -} \ No newline at end of file +} diff --git a/infra/charts/feast-feature-server/Chart.yaml b/infra/charts/feast-feature-server/Chart.yaml index 6d8744dca23..bbab077b28d 100644 --- a/infra/charts/feast-feature-server/Chart.yaml +++ b/infra/charts/feast-feature-server/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: feast-feature-server description: Feast Feature Server in Go or Python type: application -version: 0.25.1 +version: 0.25.2 keywords: - machine learning - big data diff --git a/infra/charts/feast-feature-server/README.md b/infra/charts/feast-feature-server/README.md index 41fd4c62371..ce6f32919fc 100644 --- a/infra/charts/feast-feature-server/README.md +++ b/infra/charts/feast-feature-server/README.md @@ -1,6 +1,6 @@ # Feast Python / Go Feature Server Helm Charts -Current chart version is `0.25.1` +Current chart version is `0.25.2` ## Installation @@ -30,7 +30,7 @@ See [here](https://github.com/feast-dev/feast/tree/master/examples/python-helm-d | fullnameOverride | string | `""` | | | image.pullPolicy | string | `"IfNotPresent"` | | | image.repository | string | `"feastdev/feature-server"` | Docker image for Feature Server repository | -| image.tag | string | `"0.25.1"` | The Docker image tag (can be overwritten if custom feature server deps are needed for on demand transforms) | +| image.tag | string | `"0.25.2"` | The Docker image tag (can be overwritten if custom feature server deps are needed for on demand transforms) | | imagePullSecrets | list | `[]` | | | livenessProbe.initialDelaySeconds | int | `30` | | | livenessProbe.periodSeconds | int | `30` | | diff --git a/infra/charts/feast-feature-server/values.yaml b/infra/charts/feast-feature-server/values.yaml index 782d219630d..90a216a54c3 100644 --- a/infra/charts/feast-feature-server/values.yaml +++ b/infra/charts/feast-feature-server/values.yaml @@ -9,7 +9,7 @@ image: repository: feastdev/feature-server pullPolicy: IfNotPresent # image.tag -- The Docker image tag (can be overwritten if custom feature server deps are needed for on demand transforms) - tag: 0.25.1 + tag: 0.25.2 imagePullSecrets: [] nameOverride: "" diff --git a/infra/charts/feast-python-server/Chart.yaml b/infra/charts/feast-python-server/Chart.yaml index 7e1be9ceb14..67ef3ca0a78 100644 --- a/infra/charts/feast-python-server/Chart.yaml +++ b/infra/charts/feast-python-server/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: feast-python-server description: Feast Feature Server in Python type: application -version: 0.25.1 +version: 0.25.2 keywords: - machine learning - big data diff --git a/infra/charts/feast-python-server/README.md b/infra/charts/feast-python-server/README.md index 20829e6d789..a86a950634e 100644 --- a/infra/charts/feast-python-server/README.md +++ b/infra/charts/feast-python-server/README.md @@ -2,7 +2,7 @@ > Note: this helm chart is deprecated in favor of [feast-feature-server](../feast-feature-server/README.md) -Current chart version is `0.25.1` +Current chart version is `0.25.2` ## Installation Docker repository and tag are required. Helm install example: diff --git a/infra/charts/feast/Chart.yaml b/infra/charts/feast/Chart.yaml index 2bd45f334f2..ec6795897b0 100644 --- a/infra/charts/feast/Chart.yaml +++ b/infra/charts/feast/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v1 description: Feature store for machine learning name: feast -version: 0.25.1 +version: 0.25.2 keywords: - machine learning - big data diff --git a/infra/charts/feast/README.md b/infra/charts/feast/README.md index 592d5f053fb..f679ea3476b 100644 --- a/infra/charts/feast/README.md +++ b/infra/charts/feast/README.md @@ -8,7 +8,7 @@ This repo contains Helm charts for Feast Java components that are being installe ## Chart: Feast -Feature store for machine learning Current chart version is `0.25.1` +Feature store for machine learning Current chart version is `0.25.2` ## Installation @@ -65,8 +65,8 @@ See [here](https://github.com/feast-dev/feast/tree/master/examples/java-demo) fo | Repository | Name | Version | |------------|------|---------| | https://charts.helm.sh/stable | redis | 10.5.6 | -| https://feast-helm-charts.storage.googleapis.com | feature-server(feature-server) | 0.25.1 | -| https://feast-helm-charts.storage.googleapis.com | transformation-service(transformation-service) | 0.25.1 | +| https://feast-helm-charts.storage.googleapis.com | feature-server(feature-server) | 0.25.2 | +| https://feast-helm-charts.storage.googleapis.com | transformation-service(transformation-service) | 0.25.2 | ## Values diff --git a/infra/charts/feast/charts/feature-server/Chart.yaml b/infra/charts/feast/charts/feature-server/Chart.yaml index fcb4444b819..796461ffba7 100644 --- a/infra/charts/feast/charts/feature-server/Chart.yaml +++ b/infra/charts/feast/charts/feature-server/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v1 description: "Feast Feature Server: Online feature serving service for Feast" name: feature-server -version: 0.25.1 -appVersion: v0.25.1 +version: 0.25.2 +appVersion: v0.25.2 keywords: - machine learning - big data diff --git a/infra/charts/feast/charts/feature-server/README.md b/infra/charts/feast/charts/feature-server/README.md index c4703c3a179..b31311ab090 100644 --- a/infra/charts/feast/charts/feature-server/README.md +++ b/infra/charts/feast/charts/feature-server/README.md @@ -1,6 +1,6 @@ # feature-server -![Version: 0.25.1](https://img.shields.io/badge/Version-0.25.1-informational?style=flat-square) ![AppVersion: v0.25.1](https://img.shields.io/badge/AppVersion-v0.25.1-informational?style=flat-square) +![Version: 0.25.2](https://img.shields.io/badge/Version-0.25.2-informational?style=flat-square) ![AppVersion: v0.25.2](https://img.shields.io/badge/AppVersion-v0.25.2-informational?style=flat-square) Feast Feature Server: Online feature serving service for Feast @@ -17,7 +17,7 @@ Feast Feature Server: Online feature serving service for Feast | envOverrides | object | `{}` | Extra environment variables to set | | image.pullPolicy | string | `"IfNotPresent"` | Image pull policy | | image.repository | string | `"feastdev/feature-server-java"` | Docker image for Feature Server repository | -| image.tag | string | `"0.25.1"` | Image tag | +| image.tag | string | `"0.25.2"` | Image tag | | ingress.grpc.annotations | object | `{}` | Extra annotations for the ingress | | ingress.grpc.auth.enabled | bool | `false` | Flag to enable auth | | ingress.grpc.class | string | `"nginx"` | Which ingress controller to use | diff --git a/infra/charts/feast/charts/feature-server/values.yaml b/infra/charts/feast/charts/feature-server/values.yaml index 35380787bc6..617ecf34455 100644 --- a/infra/charts/feast/charts/feature-server/values.yaml +++ b/infra/charts/feast/charts/feature-server/values.yaml @@ -5,7 +5,7 @@ image: # image.repository -- Docker image for Feature Server repository repository: feastdev/feature-server-java # image.tag -- Image tag - tag: 0.25.1 + tag: 0.25.2 # image.pullPolicy -- Image pull policy pullPolicy: IfNotPresent diff --git a/infra/charts/feast/charts/transformation-service/Chart.yaml b/infra/charts/feast/charts/transformation-service/Chart.yaml index 878ed7e0380..b6b5c001390 100644 --- a/infra/charts/feast/charts/transformation-service/Chart.yaml +++ b/infra/charts/feast/charts/transformation-service/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v1 description: "Transformation service: to compute on-demand features" name: transformation-service -version: 0.25.1 -appVersion: v0.25.1 +version: 0.25.2 +appVersion: v0.25.2 keywords: - machine learning - big data diff --git a/infra/charts/feast/charts/transformation-service/README.md b/infra/charts/feast/charts/transformation-service/README.md index fea87a55e0b..8fdc8292afb 100644 --- a/infra/charts/feast/charts/transformation-service/README.md +++ b/infra/charts/feast/charts/transformation-service/README.md @@ -1,6 +1,6 @@ # transformation-service -![Version: 0.25.1](https://img.shields.io/badge/Version-0.25.1-informational?style=flat-square) ![AppVersion: v0.25.1](https://img.shields.io/badge/AppVersion-v0.25.1-informational?style=flat-square) +![Version: 0.25.2](https://img.shields.io/badge/Version-0.25.2-informational?style=flat-square) ![AppVersion: v0.25.2](https://img.shields.io/badge/AppVersion-v0.25.2-informational?style=flat-square) Transformation service: to compute on-demand features @@ -13,7 +13,7 @@ Transformation service: to compute on-demand features | envOverrides | object | `{}` | Extra environment variables to set | | image.pullPolicy | string | `"IfNotPresent"` | Image pull policy | | image.repository | string | `"feastdev/feature-transformation-server"` | Docker image for Transformation Server repository | -| image.tag | string | `"0.25.1"` | Image tag | +| image.tag | string | `"0.25.2"` | Image tag | | nodeSelector | object | `{}` | Node labels for pod assignment | | podLabels | object | `{}` | Labels to be added to Feast Serving pods | | replicaCount | int | `1` | Number of pods that will be created | diff --git a/infra/charts/feast/charts/transformation-service/values.yaml b/infra/charts/feast/charts/transformation-service/values.yaml index 1bf259ec187..72e335130ba 100644 --- a/infra/charts/feast/charts/transformation-service/values.yaml +++ b/infra/charts/feast/charts/transformation-service/values.yaml @@ -5,7 +5,7 @@ image: # image.repository -- Docker image for Transformation Server repository repository: feastdev/feature-transformation-server # image.tag -- Image tag - tag: 0.25.1 + tag: 0.25.2 # image.pullPolicy -- Image pull policy pullPolicy: IfNotPresent diff --git a/infra/charts/feast/requirements.yaml b/infra/charts/feast/requirements.yaml index 20d89e3669a..bda09b020a0 100644 --- a/infra/charts/feast/requirements.yaml +++ b/infra/charts/feast/requirements.yaml @@ -1,12 +1,12 @@ dependencies: - name: feature-server alias: feature-server - version: 0.25.1 + version: 0.25.2 condition: feature-server.enabled repository: https://feast-helm-charts.storage.googleapis.com - name: transformation-service alias: transformation-service - version: 0.25.1 + version: 0.25.2 condition: transformation-service.enabled repository: https://feast-helm-charts.storage.googleapis.com - name: redis diff --git a/java/pom.xml b/java/pom.xml index aa6157a14ff..8568e3bb98c 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -35,7 +35,7 @@ - 0.25.1 + 0.25.2 https://github.com/feast-dev/feast UTF-8 diff --git a/protos/feast/core/DataSource.proto b/protos/feast/core/DataSource.proto index 5258618f3bd..3992d2c247d 100644 --- a/protos/feast/core/DataSource.proto +++ b/protos/feast/core/DataSource.proto @@ -23,6 +23,7 @@ option java_outer_classname = "DataSourceProto"; option java_package = "feast.proto.core"; import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; import "feast/core/DataFormat.proto"; import "feast/types/Value.proto"; import "feast/core/Feature.proto"; @@ -89,6 +90,12 @@ message DataSource { // Optional batch source for streaming sources for historical features and materialization. DataSource batch_source = 26; + SourceMeta meta = 50; + + message SourceMeta { + google.protobuf.Timestamp earliestEventTimestamp = 1; + google.protobuf.Timestamp latestEventTimestamp = 2; + } // Defines options for DataSource that sources features from a file message FileOptions { diff --git a/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile b/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile index 990c1fd8f03..c95c515fb4b 100644 --- a/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile +++ b/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile @@ -8,8 +8,8 @@ RUN apt update && \ build-essential RUN pip install pip --upgrade -COPY . . -RUN pip install ".[aws,gcp,snowflake,redis,go,mysql,postgres]" +RUN pip install "feast[aws,gcp,snowflake,redis,go,mysql,postgres]" + RUN apt update RUN apt install -y -V ca-certificates lsb-release wget diff --git a/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile.dev b/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile.dev index 990c1fd8f03..ecbc199a5b9 100644 --- a/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile.dev +++ b/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile.dev @@ -9,7 +9,8 @@ RUN apt update && \ RUN pip install pip --upgrade COPY . . -RUN pip install ".[aws,gcp,snowflake,redis,go,mysql,postgres]" + +RUN pip install "feast[aws,gcp,snowflake,redis,go,mysql,postgres]" RUN apt update RUN apt install -y -V ca-certificates lsb-release wget diff --git a/sdk/python/feast/infra/registry/base_registry.py b/sdk/python/feast/infra/registry/base_registry.py index e1e9ba99e18..fc65932ea16 100644 --- a/sdk/python/feast/infra/registry/base_registry.py +++ b/sdk/python/feast/infra/registry/base_registry.py @@ -638,9 +638,12 @@ def to_dict(self, project: str) -> Dict[str, List[Any]]: self.list_stream_feature_views(project=project), key=lambda stream_feature_view: stream_feature_view.name, ): - registry_dict["streamFeatureViews"].append( - self._message_to_sorted_dict(stream_feature_view.to_proto()) - ) + sfv_dict = self._message_to_sorted_dict(stream_feature_view.to_proto()) + + sfv_dict["spec"]["userDefinedFunction"][ + "body" + ] = stream_feature_view.udf_string + registry_dict["streamFeatureViews"].append(sfv_dict) for saved_dataset in sorted( self.list_saved_datasets(project=project), key=lambda item: item.name ): diff --git a/sdk/python/feast/stream_feature_view.py b/sdk/python/feast/stream_feature_view.py index 176f38d093f..d3a2164788f 100644 --- a/sdk/python/feast/stream_feature_view.py +++ b/sdk/python/feast/stream_feature_view.py @@ -71,6 +71,7 @@ class StreamFeatureView(FeatureView): timestamp_field: str materialization_intervals: List[Tuple[datetime, datetime]] udf: Optional[FunctionType] + udf_string: Optional[str] def __init__( self, @@ -88,6 +89,7 @@ def __init__( mode: Optional[str] = "spark", timestamp_field: Optional[str] = "", udf: Optional[FunctionType] = None, + udf_string: Optional[str] = "", ): if not flags_helper.is_test(): warnings.warn( @@ -114,6 +116,7 @@ def __init__( self.mode = mode or "" self.timestamp_field = timestamp_field or "" self.udf = udf + self.udf_string = udf_string super().__init__( name=name, @@ -143,6 +146,7 @@ def __eq__(self, other): self.mode != other.mode or self.timestamp_field != other.timestamp_field or self.udf.__code__.co_code != other.udf.__code__.co_code + or self.udf_string != other.udf_string or self.aggregations != other.aggregations ): return False @@ -171,6 +175,7 @@ def to_proto(self): udf_proto = UserDefinedFunctionProto( name=self.udf.__name__, body=dill.dumps(self.udf, recurse=True), + body_text=self.udf_string, ) spec = StreamFeatureViewSpecProto( name=self.name, @@ -209,6 +214,11 @@ def from_proto(cls, sfv_proto): if sfv_proto.spec.HasField("user_defined_function") else None ) + udf_string = ( + sfv_proto.spec.user_defined_function.body_text + if sfv_proto.spec.HasField("user_defined_function") + else None + ) stream_feature_view = cls( name=sfv_proto.spec.name, description=sfv_proto.spec.description, @@ -226,6 +236,7 @@ def from_proto(cls, sfv_proto): source=stream_source, mode=sfv_proto.spec.mode, udf=udf, + udf_string=udf_string, aggregations=[ Aggregation.from_proto(agg_proto) for agg_proto in sfv_proto.spec.aggregations @@ -315,6 +326,7 @@ def mainify(obj): obj.__module__ = "__main__" def decorator(user_function): + udf_string = dill.source.getsource(user_function) mainify(user_function) stream_feature_view_obj = StreamFeatureView( name=user_function.__name__, @@ -323,6 +335,7 @@ def decorator(user_function): source=source, schema=schema, udf=user_function, + udf_string=udf_string, description=description, tags=tags, online=online, diff --git a/sdk/python/feast/ui/README.md b/sdk/python/feast/ui/README.md index 0c11dcf134c..28290f0326a 100644 --- a/sdk/python/feast/ui/README.md +++ b/sdk/python/feast/ui/README.md @@ -22,4 +22,20 @@ It is used by the `feast ui` command to scaffold a local UI server. The feast py The `feast ui` command will generate the necessary `projects-list.json` file and initialize it for the UI to read. -**Note**: yarn start will not work on this because of the above dependency. +**Note**: `yarn start` will not work on this because of the above dependency. + +## Dev +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/sdk/python/feast/ui/package.json b/sdk/python/feast/ui/package.json index 358aa2cdd24..a75d7be1ffd 100644 --- a/sdk/python/feast/ui/package.json +++ b/sdk/python/feast/ui/package.json @@ -4,9 +4,9 @@ "private": true, "dependencies": { "@elastic/datemath": "^5.0.3", - "@elastic/eui": "^57.0.0", + "@elastic/eui": "^55.0.1", "@emotion/react": "^11.9.0", - "@feast-dev/feast-ui": "latest", + "@feast-dev/feast-ui": "0.25.2", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.2.0", "@testing-library/user-event": "^13.5.0", @@ -16,11 +16,11 @@ "moment": "^2.29.4", "prop-types": "^15.8.1", "query-string": "^7.1.1", - "react": "^18.1.0", - "react-dom": "^18.1.0", - "react-query": "^3.39.0", - "react-router-dom": "^6.3.0", - "react-scripts": "5.0.1", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-query": "^3.34.12", + "react-router-dom": "6", + "react-scripts": "^5.0.0", "typescript": "^4.6.4", "use-query-params": "^1.2.3", "web-vitals": "^2.1.4", diff --git a/sdk/python/feast/ui/yarn.lock b/sdk/python/feast/ui/yarn.lock index df2bfe45ff2..0793fed1c50 100644 --- a/sdk/python/feast/ui/yarn.lock +++ b/sdk/python/feast/ui/yarn.lock @@ -1202,51 +1202,6 @@ uuid "^8.3.0" vfile "^4.2.0" -"@elastic/eui@^57.0.0": - version "57.0.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-57.0.0.tgz#86d43e27196f9997ef44d2a4c701d092ce99e132" - integrity sha512-VBgW6Pr0JJB3JhJ59MV8guxb2v4Gd3SJEmsMGKGyIY+KcvSMWbVEGa44Ep12VAJYynIA05Z3OXXc/ge5dMycpA== - dependencies: - "@types/chroma-js" "^2.0.0" - "@types/lodash" "^4.14.160" - "@types/numeral" "^0.0.28" - "@types/react-beautiful-dnd" "^13.1.2" - "@types/react-input-autosize" "^2.2.1" - "@types/react-virtualized-auto-sizer" "^1.0.1" - "@types/react-window" "^1.8.5" - "@types/refractor" "^3.0.0" - "@types/resize-observer-browser" "^0.1.5" - "@types/vfile-message" "^2.0.0" - chroma-js "^2.1.0" - classnames "^2.2.6" - lodash "^4.17.21" - mdast-util-to-hast "^10.0.0" - numeral "^2.0.6" - prop-types "^15.6.0" - react-beautiful-dnd "^13.1.0" - react-dropzone "^11.5.3" - react-element-to-jsx-string "^14.3.4" - react-focus-on "^3.5.4" - react-input-autosize "^3.0.0" - react-is "^17.0.2" - react-virtualized-auto-sizer "^1.0.6" - react-window "^1.8.6" - refractor "^3.5.0" - rehype-raw "^5.0.0" - rehype-react "^6.0.0" - rehype-stringify "^8.0.0" - remark-breaks "^2.0.2" - remark-emoji "^2.1.0" - remark-parse "^8.0.3" - remark-rehype "^8.0.0" - tabbable "^5.2.1" - text-diff "^1.0.1" - unified "^9.2.0" - unist-util-visit "^2.0.3" - url-parse "^1.5.10" - uuid "^8.3.0" - vfile "^4.2.0" - "@emotion/babel-plugin@^11.7.1": version "11.9.2" resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz#723b6d394c89fb2ef782229d92ba95a740576e95" @@ -1345,10 +1300,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@feast-dev/feast-ui@latest": - version "0.24.0" - resolved "https://registry.yarnpkg.com/@feast-dev/feast-ui/-/feast-ui-0.24.0.tgz#a52037247563290f92d0d993fcaf0d88e9741f36" - integrity sha512-Te27bSVFp7gCE7+p9bbCkCEQ7+nsRCzBtwWivNPBFRn8HC2ewBzmRzzasXlCHok1cXHDbh7Xj7y+2Hshp91LTg== +"@feast-dev/feast-ui@0.25.2": + version "0.25.2" + resolved "https://registry.yarnpkg.com/@feast-dev/feast-ui/-/feast-ui-0.25.2.tgz#7dfd132f86d7ee31cc52ade1a3c44ea8ece060b1" + integrity sha512-0qFhgpJqh2HDl79oFp26HWq+iI0kNYzSaQ0fgwWmt7MxCxz6HMzEZxn1mZh0mQ8iOPKYUpxHA5QO2cUjgimk9g== dependencies: "@elastic/datemath" "^5.0.3" "@elastic/eui" "^55.0.1" @@ -1362,6 +1317,7 @@ inter-ui "^3.19.3" moment "^2.29.1" prop-types "^15.8.1" + protobufjs "^7.1.1" query-string "^7.1.1" react-query "^3.34.12" react-router-dom "6" @@ -1710,6 +1666,59 @@ schema-utils "^3.0.0" source-map "^0.7.3" +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + "@rollup/plugin-babel@^5.2.0": version "5.3.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" @@ -2373,6 +2382,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.32.tgz#51d59d7a90ef2d0ae961791e0900cad2393a0149" integrity sha512-eAIcfAvhf/BkHcf4pkLJ7ECpBAhh9kcxRBpip9cTiO+hf+aJrsxYxBeS6OXvOd9WqNAJmavXVpZvY1rBjNsXmw== +"@types/node@>=13.7.0": + version "18.8.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.8.3.tgz#ce750ab4017effa51aed6a7230651778d54e327c" + integrity sha512-0os9vz6BpGwxGe9LOhgP/ncvYN5Tx1fNcd2TM3rD/aCGBkysb+ZWpXEocG24h6ZzOi13+VB8HndAQFezsSOw1w== + "@types/node@^16.7.13": version "16.11.34" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.34.tgz#520224e4be4448c279ecad09639ab460cc441a50" @@ -6947,6 +6961,11 @@ lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +long@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.0.tgz#2696dadf4b4da2ce3f6f6b89186085d94d52fd61" + integrity sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w== + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -8252,6 +8271,24 @@ property-information@^5.0.0, property-information@^5.3.0: dependencies: xtend "^4.0.0" +protobufjs@^7.1.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.1.2.tgz#a0cf6aeaf82f5625bffcf5a38b7cd2a7de05890c" + integrity sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -8403,13 +8440,14 @@ react-dev-utils@^12.0.1: strip-ansi "^6.0.1" text-table "^0.2.0" -react-dom@^18.1.0: - version "18.1.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.1.0.tgz#7f6dd84b706408adde05e1df575b3a024d7e8a2f" - integrity sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w== +react-dom@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" + integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== dependencies: loose-envify "^1.1.0" - scheduler "^0.22.0" + object-assign "^4.1.1" + scheduler "^0.20.2" react-dropzone@^11.5.3: version "11.7.1" @@ -8481,7 +8519,7 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.1.0.tgz#61aaed3096d30eacf2a2127118b5b41387d32a67" integrity sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg== -react-query@^3.34.12, react-query@^3.39.0: +react-query@^3.34.12: version "3.39.0" resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.0.tgz#0caca7b0da98e65008bbcd4df0d25618c2100050" integrity sha512-Od0IkSuS79WJOhzWBx/ys0x13+7wFqgnn64vBqqAAnZ9whocVhl/y1padD5uuZ6EIkXbFbInax0qvY7zGM0thA== @@ -8526,7 +8564,7 @@ react-remove-scroll@^2.5.2: use-callback-ref "^1.3.0" use-sidecar "^1.1.2" -react-router-dom@6, react-router-dom@^6.3.0: +react-router-dom@6: version "6.3.0" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.3.0.tgz#a0216da813454e521905b5fa55e0e5176123f43d" integrity sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw== @@ -8541,7 +8579,7 @@ react-router@6.3.0: dependencies: history "^5.2.0" -react-scripts@5.0.1, react-scripts@^5.0.0: +react-scripts@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-5.0.1.tgz#6285dbd65a8ba6e49ca8d651ce30645a6d980003" integrity sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ== @@ -8618,12 +8656,13 @@ react-window@^1.8.6: "@babel/runtime" "^7.0.0" memoize-one ">=3.1.1 <6" -react@^18.1.0: - version "18.1.0" - resolved "https://registry.yarnpkg.com/react/-/react-18.1.0.tgz#6f8620382decb17fdc5cc223a115e2adbf104890" - integrity sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ== +react@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== dependencies: loose-envify "^1.1.0" + object-assign "^4.1.1" readable-stream@^2.0.1: version "2.3.7" @@ -9001,12 +9040,13 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -scheduler@^0.22.0: - version "0.22.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.22.0.tgz#83a5d63594edf074add9a7198b1bae76c3db01b8" - integrity sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ== +scheduler@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== dependencies: loose-envify "^1.1.0" + object-assign "^4.1.1" schema-utils@2.7.0: version "2.7.0" diff --git a/sdk/python/feast/ui_server.py b/sdk/python/feast/ui_server.py index 4d1fd67dc1e..f79030e8d35 100644 --- a/sdk/python/feast/ui_server.py +++ b/sdk/python/feast/ui_server.py @@ -30,14 +30,14 @@ def get_app( ) # Asynchronously refresh registry, notifying shutdown and canceling the active timer if the app is shutting down - registry_json = "" + registry_proto = None shutting_down = False active_timer: Optional[threading.Timer] = None def async_refresh(): store.refresh_registry() - nonlocal registry_json - registry_json = get_registry_dump(store.config, store.repo_path) + nonlocal registry_proto + registry_proto = store.registry.proto() if shutting_down: return nonlocal active_timer @@ -70,7 +70,10 @@ def shutdown_event(): @app.get("/registry") def read_registry(): - return json.loads(registry_json) + return Response( + content=registry_proto.SerializeToString(), + media_type="application/octet-stream", + ) # For all other paths (such as paths that would otherwise be handled by react router), pass to React @app.api_route("/p/{path_name:path}", methods=["GET"]) diff --git a/ui/.gitignore b/ui/.gitignore new file mode 100644 index 00000000000..728f2aab717 --- /dev/null +++ b/ui/.gitignore @@ -0,0 +1,2 @@ +src/protos.d.ts +src/protos.js diff --git a/ui/feature_repo/test_get_features.py b/ui/feature_repo/test_get_features.py deleted file mode 100644 index 722c5a3dd46..00000000000 --- a/ui/feature_repo/test_get_features.py +++ /dev/null @@ -1,86 +0,0 @@ -import pandas as pd -from great_expectations.core.expectation_suite import ExpectationSuite -from great_expectations.dataset import PandasDataset - -from feast import FeatureStore -from feast.dqm.profilers.ge_profiler import ge_profiler -from feast.infra.offline_stores.file import SavedDatasetFileStorage - -DELTA = 0.1 # controlling allowed window in fraction of the value on scale [0, 1] -# Note: the GE integration allows asserting differences between datasets. The "ds" below is the reference dataset to check and this generates the expectation suite which can be used against future datasets. -# It's used via ge.validate(new_dataset, ExpectationSuite) -# For this demo though, we ignore this and - - -@ge_profiler -def credit_profiler(ds: PandasDataset) -> ExpectationSuite: - # simple checks on data consistency - ds.expect_column_values_to_be_between( - "credit_card_due", min_value=0, mostly=0.99, # allow some outliers - ) - - ds.expect_column_values_to_be_between( - "missed_payments_1y", - min_value=0, - max_value=5, - mostly=0.99, # allow some outliers - ) - - return ds.get_expectation_suite() - - -def generate_saved_dataset(): - store = FeatureStore(repo_path=".") - entity_df = pd.read_parquet(path="data/loan_table.parquet") - - fs = store.get_feature_service("credit_score_v1") - job = store.get_historical_features(entity_df=entity_df, features=fs,) - store.create_saved_dataset( - from_=job, - name="my_training_ds", - storage=SavedDatasetFileStorage(path="my_training_ds.parquet"), - feature_service=fs, - profiler=credit_profiler, - ) - - -def get_latest_timestamps(): - store = FeatureStore(repo_path=".") - feature_views = store.list_feature_views() - for fv in feature_views: - print( - f"Data source latest event for {fv.name} is {fv.batch_source._meta.latest_event_timestamp}" - ) - - -def test_ge(): - store = FeatureStore(repo_path=".") - - print("--- Historical features (from saved dataset) ---") - ds = store.get_saved_dataset("my_training_ds") - print(ds._profile) - - -def run_demo(): - store = FeatureStore(repo_path=".") - - print("--- Historical features (from saved dataset) ---") - ds = store.get_saved_dataset("my_training_ds") - print(ds.to_df()) - - print("\n--- Online features ---") - features = store.get_online_features( - features=store.get_feature_service("credit_score_v3"), - entity_rows=[ - {"zipcode": 30721, "dob_ssn": "19530219_5179", "transaction_amt": 1023} - ], - ).to_dict() - for key, value in sorted(features.items()): - print(key, " : ", value) - - -if __name__ == "__main__": - generate_saved_dataset() - get_latest_timestamps() - # test_ge() - run_demo() diff --git a/ui/package.json b/ui/package.json index bcb5af1fa8b..24895f970e3 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "@feast-dev/feast-ui", - "version": "0.25.1", + "version": "0.25.2", "private": false, "files": [ "dist" @@ -41,6 +41,7 @@ "inter-ui": "^3.19.3", "moment": "^2.29.1", "prop-types": "^15.8.1", + "protobufjs": "^7.1.1", "query-string": "^7.1.1", "react-query": "^3.34.12", "react-router-dom": "6", @@ -49,12 +50,13 @@ "zod": "^3.11.6" }, "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "build:lib": "rimraf ./dist && tsc && rollup -c", - "build:lib-dev": "rimraf ./dist && tsc && rollup -c && yalc publish -f", - "test": "react-scripts test", - "eject": "react-scripts eject" + "start": "npm run generate-protos && react-scripts start", + "build": "npm run generate-protos && react-scripts build", + "build:lib": "npm run generate-protos && rimraf ./dist && tsc && rollup -c", + "build:lib-dev": "npm run generate-protos && rimraf ./dist && tsc && rollup -c && yalc publish -f", + "test": "npm run generate-protos && react-scripts test", + "eject": "react-scripts eject", + "generate-protos": "pbjs --no-encode -o src/protos.js -w commonjs -t static-module `find ../protos/feast/ -iname *.proto` && pbts -n protos -o src/protos.d.ts src/protos.js" }, "eslintConfig": { "extends": [ @@ -92,6 +94,7 @@ "@testing-library/react": "^12.0.0", "@testing-library/user-event": "^13.2.1", "msw": "^0.36.8", + "protobufjs-cli": "^1.0.2", "react": "^17.0.2", "react-dom": "^17.0.2", "rimraf": "^3.0.2", diff --git a/ui/public/projects-list.json b/ui/public/projects-list.json index d3d7c3b7d91..8e2bc3c616a 100644 --- a/ui/public/projects-list.json +++ b/ui/public/projects-list.json @@ -4,7 +4,7 @@ "name": "Credit Score Project", "description": "Project for credit scoring team and associated models.", "id": "credit_score_project", - "registryPath": "/registry.json" + "registryPath": "/registry.db" }, { "name": "Empty Registry", diff --git a/ui/public/registry.db b/ui/public/registry.db new file mode 100644 index 00000000000..617771999c7 Binary files /dev/null and b/ui/public/registry.db differ diff --git a/ui/public/registry.json b/ui/public/registry.json deleted file mode 100644 index 279c9d08327..00000000000 --- a/ui/public/registry.json +++ /dev/null @@ -1,706 +0,0 @@ -{ - "dataSources": [ - { - "createdTimestampColumn": "created_timestamp", - "fileOptions": { - "uri": "data/credit_history.parquet" - }, - "name": "credit_history", - "timestampField": "event_timestamp", - "type": "BATCH_FILE" - }, - { - "name": "transaction", - "requestDataOptions": { - "schema": [ - { - "name": "transaction_amt", - "valueType": "INT64" - } - ] - }, - "type": "REQUEST_SOURCE" - }, - { - "createdTimestampColumn": "created_timestamp", - "fileOptions": { - "uri": "data/zipcode_table.parquet" - }, - "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": [ - { - "meta": { - "createdTimestamp": "2022-05-11T19:27:03.171062Z", - "lastUpdatedTimestamp": "2022-05-11T19:27:03.171062Z" - }, - "spec": { - "joinKey": "__dummy_id", - "name": "__dummy", - "valueType": "STRING" - } - }, - { - "meta": { - "createdTimestamp": "2022-05-11T19:27:03.171112Z", - "lastUpdatedTimestamp": "2022-05-11T19:27:03.171112Z" - }, - "spec": { - "description": "Date of birth and last four digits of social security number", - "joinKey": "dob_ssn", - "name": "dob_ssn", - "tags": { - "owner": "tony@tecton.ai", - "team": "hack week" - }, - "valueType": "STRING" - } - }, - { - "meta": { - "createdTimestamp": "2022-05-11T19:27:03.171153Z", - "lastUpdatedTimestamp": "2022-05-11T19:27:03.171153Z" - }, - "spec": { - "description": "A zipcode", - "joinKey": "zipcode", - "name": "zipcode", - "tags": { - "owner": "danny@tecton.ai", - "team": "hack week" - }, - "valueType": "INT64" - } - } - ], - "featureServices": [ - { - "meta": { - "createdTimestamp": "2022-05-11T19:27:03.172623Z", - "lastUpdatedTimestamp": "2022-05-11T19:27:03.172623Z" - }, - "spec": { - "description": "Credit scoring model", - "features": [ - { - "featureColumns": [ - { - "name": "credit_card_due", - "valueType": "INT64" - }, - { - "name": "missed_payments_1y", - "valueType": "INT64" - } - ], - "featureViewName": "credit_history" - }, - { - "featureColumns": [ - { - "name": "city", - "valueType": "STRING" - }, - { - "name": "state", - "valueType": "STRING" - }, - { - "name": "location_type", - "valueType": "STRING" - }, - { - "name": "tax_returns_filed", - "valueType": "INT64" - }, - { - "name": "population", - "valueType": "INT64" - }, - { - "name": "total_wages", - "valueType": "INT64" - } - ], - "featureViewName": "zipcode_features" - } - ], - "name": "credit_score_v1", - "tags": { - "owner": "tony@tecton.ai", - "stage": "staging" - } - } - }, - { - "meta": { - "createdTimestamp": "2022-05-11T19:27:03.172405Z", - "lastUpdatedTimestamp": "2022-05-11T19:27:03.172405Z" - }, - "spec": { - "description": "Credit scoring model", - "features": [ - { - "featureColumns": [ - { - "name": "credit_card_due", - "valueType": "INT64" - }, - { - "name": "mortgage_due", - "valueType": "INT64" - }, - { - "name": "missed_payments_1y", - "valueType": "INT64" - } - ], - "featureViewName": "credit_history" - }, - { - "featureColumns": [ - { - "name": "city", - "valueType": "STRING" - }, - { - "name": "state", - "valueType": "STRING" - }, - { - "name": "location_type", - "valueType": "STRING" - }, - { - "name": "tax_returns_filed", - "valueType": "INT64" - }, - { - "name": "population", - "valueType": "INT64" - }, - { - "name": "total_wages", - "valueType": "INT64" - } - ], - "featureViewName": "zipcode_features" - } - ], - "name": "credit_score_v2", - "tags": { - "owner": "tony@tecton.ai", - "stage": "prod" - } - } - }, - { - "meta": { - "createdTimestamp": "2022-05-11T19:27:03.172264Z", - "lastUpdatedTimestamp": "2022-05-11T19:27:03.172264Z" - }, - "spec": { - "description": "Credit scoring model", - "features": [ - { - "featureColumns": [ - { - "name": "credit_card_due", - "valueType": "INT64" - }, - { - "name": "mortgage_due", - "valueType": "INT64" - }, - { - "name": "missed_payments_1y", - "valueType": "INT64" - } - ], - "featureViewName": "credit_history" - }, - { - "featureColumns": [ - { - "name": "city", - "valueType": "STRING" - }, - { - "name": "state", - "valueType": "STRING" - }, - { - "name": "location_type", - "valueType": "STRING" - }, - { - "name": "tax_returns_filed", - "valueType": "INT64" - }, - { - "name": "population", - "valueType": "INT64" - }, - { - "name": "total_wages", - "valueType": "INT64" - } - ], - "featureViewName": "zipcode_features" - }, - { - "featureColumns": [ - { - "name": "transaction_gt_last_credit_card_due", - "valueType": "BOOL" - } - ], - "featureViewName": "transaction_gt_last_credit_card_due" - } - ], - "name": "credit_score_v3", - "tags": { - "owner": "tony@tecton.ai", - "stage": "dev" - } - } - }, - { - "meta": { - "createdTimestamp": "2022-05-11T19:27:03.172530Z", - "lastUpdatedTimestamp": "2022-05-11T19:27:03.172530Z" - }, - "spec": { - "description": "Location model", - "features": [ - { - "featureColumns": [ - { - "name": "city", - "valueType": "STRING" - }, - { - "name": "state", - "valueType": "STRING" - }, - { - "name": "location_type", - "valueType": "STRING" - }, - { - "name": "tax_returns_filed", - "valueType": "INT64" - }, - { - "name": "population", - "valueType": "INT64" - }, - { - "name": "total_wages", - "valueType": "INT64" - } - ], - "featureViewName": "zipcode_features" - } - ], - "name": "zipcode_model", - "tags": { - "owner": "amanda@tecton.ai", - "stage": "dev" - } - } - }, - { - "meta": { - "createdTimestamp": "2022-05-11T19:27:03.172188Z", - "lastUpdatedTimestamp": "2022-05-11T19:27:03.172188Z" - }, - "spec": { - "description": "Location model", - "features": [ - { - "featureColumns": [ - { - "name": "tax_returns_filed", - "valueType": "INT64" - }, - { - "name": "total_wages", - "valueType": "INT64" - } - ], - "featureViewName": "zipcode_money_features" - } - ], - "name": "zipcode_model_v2", - "tags": { - "owner": "amanda@tecton.ai", - "stage": "dev" - } - } - } - ], - "featureViews": [ - { - "meta": { - "createdTimestamp": "2022-05-11T19:27:03.171421Z", - "lastUpdatedTimestamp": "2022-05-11T19:28:21.444739Z", - "materializationIntervals": [ - { - "endTime": "2019-05-11T19:27:05Z", - "startTime": "1997-09-19T19:27:13.273753Z" - }, - { - "endTime": "2022-05-11T19:27:43Z", - "startTime": "2019-05-11T19:27:05Z" - } - ] - }, - "spec": { - "batchSource": { - "createdTimestampColumn": "created_timestamp", - "dataSourceClassType": "feast.infra.offline_stores.file_source.FileSource", - "fileOptions": { - "uri": "data/credit_history.parquet" - }, - "name": "credit_history", - "timestampField": "event_timestamp", - "type": "BATCH_FILE" - }, - "entities": [ - "dob_ssn" - ], - "features": [ - { - "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" - } - ], - "name": "credit_history", - "online": true, - "tags": { - "access_group": "feast-team@tecton.ai", - "date_added": "2022-02-6", - "experiments": "experiment-A" - }, - "ttl": "777600000s" - } - }, - { - "meta": { - "createdTimestamp": "2022-05-11T19:27:03.171300Z", - "lastUpdatedTimestamp": "2022-05-11T19:27:46.002348Z", - "materializationIntervals": [ - { - "endTime": "2019-05-11T19:27:05Z", - "startTime": "2012-05-13T19:27:08.483036Z" - }, - { - "endTime": "2022-05-11T19:27:43Z", - "startTime": "2019-05-11T19:27:05Z" - } - ] - }, - "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" - }, - "entities": [ - "zipcode" - ], - "features": [ - { - "name": "city", - "valueType": "STRING" - }, - { - "name": "state", - "valueType": "STRING" - }, - { - "name": "location_type", - "valueType": "STRING" - }, - { - "name": "tax_returns_filed", - "valueType": "INT64" - }, - { - "name": "population", - "valueType": "INT64" - }, - { - "name": "total_wages", - "valueType": "INT64" - } - ], - "name": "zipcode_features", - "online": true, - "tags": { - "access_group": "feast-team@tecton.ai", - "date_added": "2022-02-7", - "experiments": "experiment-A,experiment-B,experiment-C" - }, - "ttl": "315360000s" - } - }, - { - "meta": { - "createdTimestamp": "2022-05-11T19:27:03.171195Z", - "lastUpdatedTimestamp": "2022-05-11T19:27:45.942549Z", - "materializationIntervals": [ - { - "endTime": "2019-05-11T19:27:05Z", - "startTime": "2012-05-13T19:27:06.493847Z" - }, - { - "endTime": "2022-05-11T19:27:43Z", - "startTime": "2019-05-11T19:27:05Z" - } - ] - }, - "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" - }, - "entities": [ - "zipcode" - ], - "features": [ - { - "name": "tax_returns_filed", - "valueType": "INT64" - }, - { - "name": "total_wages", - "valueType": "INT64" - } - ], - "name": "zipcode_money_features", - "online": true, - "tags": { - "access_group": "feast-team@tecton.ai", - "date_added": "2022-02-7", - "experiments": "experiment-A,experiment-B,experiment-C" - }, - "ttl": "315360000s" - } - } - ], - "infra": [ - { - "name": "credit_scoring_aws_credit_history", - "path": "/Users/dannychiao/GitHub/feast/ui/feature_repo/data/online.db" - }, - { - "name": "credit_scoring_aws_zipcode_features", - "path": "/Users/dannychiao/GitHub/feast/ui/feature_repo/data/online.db" - }, - { - "name": "credit_scoring_aws_zipcode_money_features", - "path": "/Users/dannychiao/GitHub/feast/ui/feature_repo/data/online.db" - } - ], - "onDemandFeatureViews": [ - { - "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" - } - }, - "transaction": { - "requestDataSource": { - "name": "transaction", - "requestDataOptions": { - "schema": [ - { - "name": "transaction_amt", - "valueType": "INT64" - } - ] - }, - "type": "REQUEST_SOURCE" - } - } - }, - "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" - } - } - } - ], - "streamFeatureViews": [ - { - "meta": { - "createdTimestamp": "2022-05-11T19:27:03.171556Z", - "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": "conv_percentage", - "valueType": "FLOAT" - }, - { - "name": "acc_percentage", - "valueType": "FLOAT" - } - ], - "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": "driver_stats_stream", - "owner": "test@gmail.com", - "timestampField": "timestamp", - "type": "STREAM_KAFKA" - }, - "ttl": "86400s", - "userDefinedFunction": { - "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" - } - } - } - ], - "project": "credit_scoring_aws" -} 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/components/FeaturesInServiceDisplay.tsx b/ui/src/components/FeaturesInServiceDisplay.tsx index 39091b81dec..6ca2fcc8b09 100644 --- a/ui/src/components/FeaturesInServiceDisplay.tsx +++ b/ui/src/components/FeaturesInServiceDisplay.tsx @@ -1,36 +1,26 @@ import React from "react"; -import { z } from "zod"; import { EuiBasicTable } from "@elastic/eui"; -import { FeastFeatureInServiceType } from "../parsers/feastFeatureServices"; import EuiCustomLink from "./EuiCustomLink"; -import { FEAST_FEATURE_VALUE_TYPES } from "../parsers/types"; import { useParams } from "react-router-dom"; +import { feast } from "../protos"; interface FeatureViewsListInterace { - featureViews: FeastFeatureInServiceType[]; + featureViews: feast.core.IFeatureViewProjection[]; +} + +interface IFeatureColumnInService { + featureViewName: string; + name: string; + valueType: feast.types.ValueType.Enum; } const FeaturesInServiceList = ({ featureViews }: FeatureViewsListInterace) => { const { projectName } = useParams(); - - const FeatureInService = z.object({ - featureViewName: z.string(), - featureColumnName: z.string(), - valueType: z.nativeEnum(FEAST_FEATURE_VALUE_TYPES), - }); - type FeatureInServiceType = z.infer; - - var items: FeatureInServiceType[] = []; - featureViews.forEach((featureView) => { - featureView.featureColumns.forEach((featureColumn) => { - const row: FeatureInServiceType = { - featureViewName: featureView.featureViewName, - featureColumnName: featureColumn.name, - valueType: featureColumn.valueType, - }; - items.push(row); - }); - }); + const items: IFeatureColumnInService[] = featureViews.flatMap(featureView => featureView.featureColumns!.map(featureColumn => ({ + featureViewName: featureView.featureViewName!, + name: featureColumn.name!, + valueType: featureColumn.valueType!, + }))); const columns = [ { @@ -49,15 +39,18 @@ const FeaturesInServiceList = ({ featureViews }: FeatureViewsListInterace) => { }, { name: "Feature Column", - field: "featureColumnName", + field: "name", }, { name: "Value Type", field: "valueType", + render: (valueType: feast.types.ValueType.Enum) => { + return feast.types.ValueType.Enum[valueType]; + }, }, ]; - const getRowProps = (item: FeatureInServiceType) => { + const getRowProps = (item: IFeatureColumnInService) => { return { "data-test-subj": `row-${item.featureViewName}`, }; diff --git a/ui/src/components/FeaturesListDisplay.tsx b/ui/src/components/FeaturesListDisplay.tsx index dcb6ba81eb1..a40730c6873 100644 --- a/ui/src/components/FeaturesListDisplay.tsx +++ b/ui/src/components/FeaturesListDisplay.tsx @@ -1,38 +1,39 @@ -import React, { useContext } from "react"; -import { EuiBasicTable, EuiLoadingSpinner, EuiBadge } from "@elastic/eui"; -import { FeastFeatureColumnType } from "../parsers/feastFeatureViews"; -import useLoadFeatureViewSummaryStatistics from "../queries/useLoadFeatureViewSummaryStatistics"; -import SparklineHistogram from "./SparklineHistogram"; -import FeatureFlagsContext from "../contexts/FeatureFlagsContext"; +import { EuiBasicTable } from "@elastic/eui"; import EuiCustomLink from "./EuiCustomLink"; +import { feast } from "../protos"; interface FeaturesListProps { projectName: string; featureViewName: string; - features: FeastFeatureColumnType[]; + features: feast.core.IFeatureSpecV2[]; link: boolean; } -const FeaturesList = ({ projectName, featureViewName, features, link }: FeaturesListProps) => { - const { enabledFeatureStatistics } = useContext(FeatureFlagsContext); - const { isLoading, isError, isSuccess, data } = - useLoadFeatureViewSummaryStatistics(featureViewName); - +const FeaturesList = ({ + projectName, + featureViewName, + features, + link, +}: FeaturesListProps) => { let columns: { name: string; render?: any; field: any }[] = [ - { + { name: "Name", field: "name", - render: (item: string) => ( - ( + + to={`/p/${projectName}/feature-view/${featureViewName}/feature/${item}`} + > {item} - ) + ), }, { name: "Value Type", field: "valueType", + render: (valueType: feast.types.ValueType.Enum) => { + return feast.types.ValueType.Enum[valueType]; + }, }, ]; @@ -40,50 +41,7 @@ const FeaturesList = ({ projectName, featureViewName, features, link }: Features columns[0].render = undefined; } - if (enabledFeatureStatistics) { - columns.push( - ...[ - { - name: "Sample", - field: "", - render: (item: FeastFeatureColumnType) => { - const statistics = - isSuccess && data && data.columnsSummaryStatistics[item.name]; - - return ( - - {isLoading && } - {isError && ( - error loading samples - )} - {statistics && statistics.sampleValues.join(",")} - - ); - }, - }, - { - name: "Sparklines", - field: "", - render: (item: FeastFeatureColumnType) => { - const statistics = - isSuccess && data && data.columnsSummaryStatistics[item.name]; - - if ( - statistics && - statistics.valueType === "INT64" && - statistics.histogram - ) { - return ; - } else { - return ""; - } - }, - }, - ] - ); - } - - const getRowProps = (item: FeastFeatureColumnType) => { + const getRowProps = (item: feast.core.IFeatureSpecV2) => { return { "data-test-subj": `row-${item.name}`, }; diff --git a/ui/src/components/NumericFeaturesTable.tsx b/ui/src/components/NumericFeaturesTable.tsx deleted file mode 100644 index 7c55f5ddbae..00000000000 --- a/ui/src/components/NumericFeaturesTable.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { EuiBasicTable } from "@elastic/eui"; -import React from "react"; -import { NumericColumnSummaryStatisticType } from "../parsers/featureViewSummaryStatistics"; -import SparklineHistogram from "./SparklineHistogram"; - -interface NumericFeaturesTableProps { - data: NumericColumnSummaryStatisticType[]; -} - -const NumericFeaturesTable = ({ data }: NumericFeaturesTableProps) => { - const columns = [ - { name: "Name", field: "name" }, - { - name: "Value Type", - field: "valueType", - }, - { - name: "Sample", - render: (statistics: NumericColumnSummaryStatisticType) => { - return ( - - {statistics && statistics.sampleValues.join(",")} - - ); - }, - }, - { - name: "Min/Max", - render: (statistics: NumericColumnSummaryStatisticType) => { - return statistics.min !== undefined && statistics.max !== undefined - ? `${statistics.min}/${statistics.max}` - : undefined; - }, - }, - { name: "zeros", field: "proportionOfZeros" }, - { name: "missing", field: "proportionMissing" }, - { - name: "Sparklines", - render: (statistics: NumericColumnSummaryStatisticType) => { - if (statistics && statistics.histogram) { - return ; - } else { - return ""; - } - }, - }, - ]; - - const getRowProps = (item: NumericColumnSummaryStatisticType) => { - return { - "data-test-subj": `row-${item.name}`, - }; - }; - - return ( - - ); -}; - -export default NumericFeaturesTable; diff --git a/ui/src/components/SparklineHistogram.tsx b/ui/src/components/SparklineHistogram.tsx deleted file mode 100644 index bd632ec20d1..00000000000 --- a/ui/src/components/SparklineHistogram.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from "react"; -import { HistogramDataType } from "../parsers/featureViewSummaryStatistics"; -import { extent } from "d3-array"; -import { scaleLinear } from "d3"; -import { EuiBadge, useEuiTheme } from "@elastic/eui"; - -interface SparklineHistogramProps { - data: HistogramDataType; -} - -const SparklineHistogram = ({ data }: SparklineHistogramProps) => { - const width = 100; - const height = 24; - - const yMax = height - 2; - - const { euiTheme } = useEuiTheme(); - - if (data.length > 0) { - const x0Extent = extent(data, (d) => d.x0) as [number, number]; - const xScale = scaleLinear() - .domain(x0Extent) - .range([0, width - width / data.length]); - - const yExtent = extent(data, (d) => d.count) as [number, number]; - const yScale = scaleLinear().domain(yExtent).range([0, yMax]); - - return ( - - - {data.map((d) => { - const barHeight = yScale(d.count); - - return ( - - ); - })} - - ); - } else { - return histogram n/a; - } -}; - -export default SparklineHistogram; diff --git a/ui/src/hooks/useFCOExploreSuggestions.ts b/ui/src/hooks/useFCOExploreSuggestions.ts index 5767f73beda..822642daf4b 100644 --- a/ui/src/hooks/useFCOExploreSuggestions.ts +++ b/ui/src/hooks/useFCOExploreSuggestions.ts @@ -1,9 +1,9 @@ import { encodeSearchQueryString } from "./encodeSearchQueryString"; import { FEAST_FCO_TYPES } from "../parsers/types"; -import { FeastFeatureViewType } from "../parsers/feastFeatureViews"; import { useParams } from "react-router-dom"; import { useFeatureViewTagsAggregation } from "./useTagsAggregation"; +import { feast } from "../protos"; interface ExplorationSuggestionItem { name: string; @@ -66,14 +66,14 @@ const sortTagsByTotalUsage = ( }; const generateExplorationSuggestions = ( - tagAggregation: Record>, + tagAggregation: Record>, projectName: string ) => { const suggestions: ExplorationSuggestion[] = []; if (tagAggregation) { const SortedCandidates = - sortTagByUniqueValues(tagAggregation); + sortTagByUniqueValues(tagAggregation); SortedCandidates.slice(0, NUMBER_OF_SUGGESTION_GROUPS).forEach( ([selectedTag, selectedTagValuesMap]) => { diff --git a/ui/src/hooks/useTagsAggregation.ts b/ui/src/hooks/useTagsAggregation.ts index 21480fa8b0c..13d0c2f9669 100644 --- a/ui/src/hooks/useTagsAggregation.ts +++ b/ui/src/hooks/useTagsAggregation.ts @@ -1,8 +1,7 @@ import { useContext, useMemo } from "react"; import RegistryPathContext from "../contexts/RegistryPathContext"; -import { FeastFeatureServiceType } from "../parsers/feastFeatureServices"; -import { FeastFeatureViewType } from "../parsers/feastFeatureViews"; import useLoadRegistry from "../queries/useLoadRegistry"; +import { feast } from "../protos"; // Usage of generic type parameter T // https://stackoverflow.com/questions/53203409/how-to-tell-typescript-that-im-returning-an-array-of-arrays-of-the-input-type @@ -44,12 +43,12 @@ const useFeatureViewTagsAggregation = () => { const data = useMemo(() => { return query.data && query.data.objects && query.data.objects.featureViews - ? buildTagCollection( - query.data.objects.featureViews, - (fv) => { - return fv.spec.tags; - } - ) + ? buildTagCollection( + query.data.objects.featureViews!, + (fv) => { + return fv.spec?.tags!; + } + ) : undefined; }, [query.data]); @@ -67,12 +66,12 @@ const useFeatureServiceTagsAggregation = () => { return query.data && query.data.objects && query.data.objects.featureServices - ? buildTagCollection( - query.data.objects.featureServices, - (fs) => { - return fs.spec.tags; - } - ) + ? buildTagCollection( + query.data.objects.featureServices, + (fs) => { + return fs.spec?.tags!; + } + ) : undefined; }, [query.data]); 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/pages/data-sources/BatchSourcePropertiesView.tsx b/ui/src/pages/data-sources/BatchSourcePropertiesView.tsx index 97f9f58d274..c19e4ff50f8 100644 --- a/ui/src/pages/data-sources/BatchSourcePropertiesView.tsx +++ b/ui/src/pages/data-sources/BatchSourcePropertiesView.tsx @@ -1,38 +1,16 @@ import React from "react"; import { - EuiCodeBlock, EuiDescriptionList, EuiDescriptionListDescription, EuiDescriptionListTitle, EuiFlexGroup, EuiFlexItem, - EuiSpacer, - EuiTitle, } from "@elastic/eui"; +import { feast } from "../../protos"; +import { toDate } from "../../utils/timestamp"; interface BatchSourcePropertiesViewProps { - batchSource: { - type?: string | undefined; - owner?: string | undefined; - description?: string | undefined; - dataSourceClassType?: string | undefined; - fileOptions?: - | { - uri?: string | undefined; - } - | undefined; - meta?: - | { - latestEventTimestamp?: Date | undefined; - earliestEventTimestamp?: Date | undefined; - } - | undefined; - bigqueryOptions?: - | { - dbtModelSerialized?: string | undefined; - } - | undefined; - }; + batchSource: feast.core.IDataSource; } const BatchSourcePropertiesView = (props: BatchSourcePropertiesViewProps) => { @@ -49,7 +27,7 @@ const BatchSourcePropertiesView = (props: BatchSourcePropertiesViewProps) => { {batchSource.dataSourceClassType.split(".").at(-1)} - ) : batchSource.type ? ( + ) : feast.core.DataSource.SourceType[batchSource.type!] ? ( {batchSource.type} @@ -87,9 +65,9 @@ const BatchSourcePropertiesView = (props: BatchSourcePropertiesViewProps) => { Latest Event - {batchSource.meta.latestEventTimestamp.toLocaleDateString( - "en-CA" - )} + {toDate( + batchSource.meta.latestEventTimestamp + ).toLocaleDateString("en-CA")} )} @@ -99,35 +77,14 @@ const BatchSourcePropertiesView = (props: BatchSourcePropertiesViewProps) => { Earliest Event - {batchSource.meta.earliestEventTimestamp.toLocaleDateString( - "en-CA" - )} + {toDate( + batchSource.meta?.earliestEventTimestamp + ).toLocaleDateString("en-CA")} )} - - {batchSource.bigqueryOptions?.dbtModelSerialized && ( - - - - )} - {batchSource.bigqueryOptions?.dbtModelSerialized && ( - - -

Dbt Transformation

-
- - {batchSource.bigqueryOptions.dbtModelSerialized} - -
- )} ); diff --git a/ui/src/pages/data-sources/DataSourceDbt.tsx b/ui/src/pages/data-sources/DataSourceDbt.tsx deleted file mode 100644 index 4e61ba80266..00000000000 --- a/ui/src/pages/data-sources/DataSourceDbt.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from "react"; -import { - EuiCodeBlock, - EuiPanel, - EuiHorizontalRule, - EuiTitle, -} from "@elastic/eui"; -import { useParams } from "react-router-dom"; -import useLoadDataSource from "./useLoadDataSource"; - -const DataSourceDbt = () => { - let { dataSourceName } = useParams(); - - const dsName = dataSourceName === undefined ? "" : dataSourceName; - - const { isSuccess, data } = useLoadDataSource(dsName); - - return isSuccess && data && data.bigqueryOptions ? ( - - -

Dbt Transformation

-
- - - {data.bigqueryOptions.dbtModelSerialized} - -
- ) : ( - - No data so sad - - ); -}; - -export default DataSourceDbt; diff --git a/ui/src/pages/data-sources/DataSourceInstance.tsx b/ui/src/pages/data-sources/DataSourceInstance.tsx index 332f51e0235..d6db4c87472 100644 --- a/ui/src/pages/data-sources/DataSourceInstance.tsx +++ b/ui/src/pages/data-sources/DataSourceInstance.tsx @@ -7,12 +7,10 @@ import { } from "@elastic/eui"; import { DataSourceIcon32 } from "../../graphics/DataSourceIcon"; -import { useMatchExact, useMatchSubpath } from "../../hooks/useMatchSubpath"; +import { useMatchExact } from "../../hooks/useMatchSubpath"; import { useDocumentTitle } from "../../hooks/useDocumentTitle"; import DataSourceRawData from "./DataSourceRawData"; import DataSourceOverviewTab from "./DataSourceOverviewTab"; -import DataSourceDbt from "./DataSourceDbt"; -import useLoadDataSource from "./useLoadDataSource"; import { useDataSourceCustomTabs, @@ -24,8 +22,6 @@ const DataSourceInstance = () => { let { dataSourceName } = useParams(); useDocumentTitle(`${dataSourceName} | Data Source | Feast`); - const dsName = dataSourceName === undefined ? "" : dataSourceName; - const { isSuccess, data } = useLoadDataSource(dsName); let tabs = [ { @@ -37,17 +33,6 @@ const DataSourceInstance = () => { }, ]; - const dbtTab = { - label: "Dbt Definition", - isSelected: useMatchSubpath("dbt"), - onClick: () => { - navigate("dbt"); - }, - }; - if (isSuccess && data?.bigqueryOptions?.dbtModelSerialized) { - tabs.push(dbtTab); - } - const { customNavigationTabs } = useDataSourceCustomTabs(navigate); tabs = tabs.concat(customNavigationTabs); @@ -72,7 +57,6 @@ const DataSourceInstance = () => { } /> } /> - } /> {CustomTabRoutes} diff --git a/ui/src/pages/data-sources/DataSourceOverviewTab.tsx b/ui/src/pages/data-sources/DataSourceOverviewTab.tsx index 124a0e6ab92..6eadfc42d0a 100644 --- a/ui/src/pages/data-sources/DataSourceOverviewTab.tsx +++ b/ui/src/pages/data-sources/DataSourceOverviewTab.tsx @@ -19,6 +19,7 @@ import BatchSourcePropertiesView from "./BatchSourcePropertiesView"; import FeatureViewEdgesList from "../entities/FeatureViewEdgesList"; import RequestDataSourceSchemaTable from "./RequestDataSourceSchemaTable"; import useLoadDataSource from "./useLoadDataSource"; +import { feast } from "../../protos"; const DataSourceOverviewTab = () => { let { dataSourceName } = useParams(); @@ -57,7 +58,7 @@ const DataSourceOverviewTab = () => { Source Type - {data.type} + {feast.core.DataSource.SourceType[data.type]} @@ -77,12 +78,12 @@ const DataSourceOverviewTab = () => { { + fields={data?.requestDataOptions?.schema!.map((obj) => { return { - fieldName: obj.name, - valueType: obj.valueType, + fieldName: obj.name!, + valueType: obj.valueType!, }; - })} + })!} /> ) : ( diff --git a/ui/src/pages/data-sources/DataSourcesListingTable.tsx b/ui/src/pages/data-sources/DataSourcesListingTable.tsx index 661d18c7e9d..50c1f933a9c 100644 --- a/ui/src/pages/data-sources/DataSourcesListingTable.tsx +++ b/ui/src/pages/data-sources/DataSourcesListingTable.tsx @@ -1,11 +1,11 @@ import React from "react"; import { EuiBasicTable } from "@elastic/eui"; import EuiCustomLink from "../../components/EuiCustomLink"; -import { FeastDatasourceType } from "../../parsers/feastDatasources"; import { useParams } from "react-router-dom"; +import { feast } from "../../protos"; interface DatasourcesListingTableProps { - dataSources: FeastDatasourceType[]; + dataSources: feast.core.IDataSource[]; } const DatasourcesListingTable = ({ @@ -33,10 +33,13 @@ const DatasourcesListingTable = ({ name: "Type", field: "type", sortable: true, + render: (valueType: feast.types.ValueType.Enum) => { + return feast.types.ValueType.Enum[valueType]; + }, }, ]; - const getRowProps = (item: FeastDatasourceType) => { + const getRowProps = (item: feast.core.IDataSource) => { return { "data-test-subj": `row-${item.name}`, }; diff --git a/ui/src/pages/data-sources/RequestDataSourceSchemaTable.tsx b/ui/src/pages/data-sources/RequestDataSourceSchemaTable.tsx index 60ef4c406a4..a55aeac0280 100644 --- a/ui/src/pages/data-sources/RequestDataSourceSchemaTable.tsx +++ b/ui/src/pages/data-sources/RequestDataSourceSchemaTable.tsx @@ -1,10 +1,10 @@ import React from "react"; import { EuiBasicTable } from "@elastic/eui"; -import { FEAST_FEATURE_VALUE_TYPES } from "../../parsers/types"; +import { feast } from "../../protos"; interface RequestDataSourceSchemaField { fieldName: string; - valueType: FEAST_FEATURE_VALUE_TYPES; + valueType: feast.types.ValueType.Enum; } interface RequestDataSourceSchema { @@ -12,7 +12,6 @@ interface RequestDataSourceSchema { } const RequestDataSourceSchemaTable = ({ fields }: RequestDataSourceSchema) => { - console.log(fields); const columns = [ { name: "Field", @@ -21,6 +20,9 @@ const RequestDataSourceSchemaTable = ({ fields }: RequestDataSourceSchema) => { { name: "Value Type", field: "valueType", + render: (valueType: feast.types.ValueType.Enum) => { + return feast.types.ValueType.Enum[valueType]; + }, }, ]; diff --git a/ui/src/pages/entities/EntitiesListingTable.tsx b/ui/src/pages/entities/EntitiesListingTable.tsx index 2e608e37692..2a017b18aac 100644 --- a/ui/src/pages/entities/EntitiesListingTable.tsx +++ b/ui/src/pages/entities/EntitiesListingTable.tsx @@ -1,12 +1,12 @@ import React from "react"; import { EuiBasicTable } from "@elastic/eui"; import EuiCustomLink from "../../components/EuiCustomLink"; -import { FeastEntityType } from "../../parsers/feastEntities"; import useFeatureViewEdgesByEntity from "./useFeatureViewEdgesByEntity"; import { useParams } from "react-router-dom"; +import { feast } from "../../protos"; interface EntitiesListingTableProps { - entities: FeastEntityType[]; + entities: feast.core.IEntity[]; } const EntitiesListingTable = ({ entities }: EntitiesListingTableProps) => { @@ -33,15 +33,15 @@ const EntitiesListingTable = ({ entities }: EntitiesListingTableProps) => { name: "Type", field: "spec.valueType", sortable: true, - render: (valueType: string) => { - return valueType; + render: (valueType: feast.types.ValueType.Enum) => { + return feast.types.ValueType.Enum[valueType]; }, }, { name: "# of FVs", - render: (item: FeastEntityType) => { + render: (item: feast.core.IEntity) => { if (isSuccess && data) { - return data[item.spec.name] ? data[item.spec.name].length : "0"; + return data[item?.spec?.name!] ? data[item?.spec?.name!].length : "0"; } else { return "."; } @@ -49,9 +49,9 @@ const EntitiesListingTable = ({ entities }: EntitiesListingTableProps) => { }, ]; - const getRowProps = (item: FeastEntityType) => { + const getRowProps = (item: feast.core.IEntity) => { return { - "data-test-subj": `row-${item.spec.name}`, + "data-test-subj": `row-${item?.spec?.name}`, }; }; diff --git a/ui/src/pages/entities/EntityOverviewTab.tsx b/ui/src/pages/entities/EntityOverviewTab.tsx index dc649c2a9fb..b4df67bba49 100644 --- a/ui/src/pages/entities/EntityOverviewTab.tsx +++ b/ui/src/pages/entities/EntityOverviewTab.tsx @@ -19,6 +19,8 @@ import TagsDisplay from "../../components/TagsDisplay"; import FeatureViewEdgesList from "./FeatureViewEdgesList"; import useFeatureViewEdgesByEntity from "./useFeatureViewEdgesByEntity"; import useLoadEntity from "./useLoadEntity"; +import { toDate } from "../../utils/timestamp"; +import { feast } from "../../protos"; const EntityOverviewTab = () => { let { entityName } = useParams(); @@ -52,17 +54,17 @@ const EntityOverviewTab = () => { Join Key - {data.spec.joinKey} + {data?.spec?.joinKey} Description - {data.spec.description} + {data?.spec?.description} Value Type - {data.spec.valueType} + {feast.types.ValueType.Enum[data?.spec?.valueType!]} @@ -71,8 +73,8 @@ const EntityOverviewTab = () => { Created - {data.meta.createdTimestamp ? ( - data.meta.createdTimestamp.toLocaleDateString("en-CA") + {data?.meta?.createdTimestamp ? ( + toDate(data.meta.createdTimestamp).toLocaleDateString("en-CA") ) : ( No createdTimestamp specified on this entity. )} @@ -80,8 +82,8 @@ const EntityOverviewTab = () => { Updated - {data.meta.lastUpdatedTimestamp ? ( - data.meta.lastUpdatedTimestamp.toLocaleDateString("en-CA") + {data?.meta?.lastUpdatedTimestamp ? ( + toDate(data.meta.lastUpdatedTimestamp).toLocaleDateString("en-CA") ) : ( No lastUpdatedTimestamp specified on this entity. )} @@ -117,8 +119,8 @@ const EntityOverviewTab = () => {

Labels

- {data.spec.labels ? ( - + {data?.spec?.tags ? ( + ) : ( No labels specified on this entity. )} diff --git a/ui/src/pages/entities/useLoadEntity.ts b/ui/src/pages/entities/useLoadEntity.ts index a1ca6d55c16..6f7bb1f59cf 100644 --- a/ui/src/pages/entities/useLoadEntity.ts +++ b/ui/src/pages/entities/useLoadEntity.ts @@ -10,8 +10,8 @@ const useLoadEntity = (entityName: string) => { registryQuery.data === undefined ? undefined : registryQuery.data.objects.entities?.find( - (fv) => fv.spec.name === entityName - ); + (fv) => fv?.spec?.name === entityName + ); return { ...registryQuery, diff --git a/ui/src/pages/feature-services/FeatureServiceListingTable.tsx b/ui/src/pages/feature-services/FeatureServiceListingTable.tsx index b865da6e23c..c81edeaeb58 100644 --- a/ui/src/pages/feature-services/FeatureServiceListingTable.tsx +++ b/ui/src/pages/feature-services/FeatureServiceListingTable.tsx @@ -5,20 +5,18 @@ import { EuiTableFieldDataColumnType, } from "@elastic/eui"; import EuiCustomLink from "../../components/EuiCustomLink"; -import { - FeastFeatureInServiceType, - FeastFeatureServiceType, -} from "../../parsers/feastFeatureServices"; import { useParams } from "react-router-dom"; +import { feast } from "../../protos"; +import { toDate } from "../../utils/timestamp"; interface FeatureServiceListingTableProps { tagKeysSet: Set; - featureServices: FeastFeatureServiceType[]; + featureServices: feast.core.IFeatureService[]; } type FeatureServiceTypeColumn = - | EuiTableFieldDataColumnType - | EuiTableComputedColumnType; + | EuiTableFieldDataColumnType + | EuiTableComputedColumnType; const FeatureServiceListingTable = ({ tagKeysSet, @@ -44,10 +42,10 @@ const FeatureServiceListingTable = ({ { name: "# of Features", field: "spec.features", - render: (featureViews: FeastFeatureInServiceType[]) => { + render: (featureViews: feast.core.IFeatureViewProjection[]) => { var numFeatures = 0; featureViews.forEach((featureView) => { - numFeatures += featureView.featureColumns.length; + numFeatures += featureView.featureColumns!.length; }); return numFeatures; }, @@ -55,8 +53,8 @@ const FeatureServiceListingTable = ({ { name: "Last updated", field: "meta.lastUpdatedTimestamp", - render: (date: Date) => { - return date ? date.toLocaleDateString("en-CA") : "n/a"; + render: (date: any) => { + return date ? toDate(date).toLocaleDateString("en-CA") : "n/a"; }, }, ]; @@ -64,10 +62,10 @@ const FeatureServiceListingTable = ({ tagKeysSet.forEach((key) => { columns.push({ name: key, - render: (item: FeastFeatureServiceType) => { + render: (item: feast.core.IFeatureService) => { let tag = n/a; - const value = item.spec.tags ? item.spec.tags[key] : undefined; + const value = item?.spec?.tags ? item.spec.tags[key] : undefined; if (value) { tag = {value}; @@ -78,9 +76,9 @@ const FeatureServiceListingTable = ({ }); }); - const getRowProps = (item: FeastFeatureServiceType) => { + const getRowProps = (item: feast.core.IFeatureService) => { return { - "data-test-subj": `row-${item.spec.name}`, + "data-test-subj": `row-${item?.spec?.name}`, }; }; diff --git a/ui/src/pages/feature-services/FeatureServiceOverviewTab.tsx b/ui/src/pages/feature-services/FeatureServiceOverviewTab.tsx index ea62b3b3a70..387320778ff 100644 --- a/ui/src/pages/feature-services/FeatureServiceOverviewTab.tsx +++ b/ui/src/pages/feature-services/FeatureServiceOverviewTab.tsx @@ -19,6 +19,7 @@ import TagsDisplay from "../../components/TagsDisplay"; import { encodeSearchQueryString } from "../../hooks/encodeSearchQueryString"; import FeatureViewEdgesList from "../entities/FeatureViewEdgesList"; import useLoadFeatureService from "./useLoadFeatureService"; +import { toDate } from "../../utils/timestamp"; const FeatureServiceOverviewTab = () => { let { featureServiceName, projectName } = useParams(); @@ -32,9 +33,9 @@ const FeatureServiceOverviewTab = () => { let numFeatures = 0; let numFeatureViews = 0; if (data) { - data.spec.features.forEach((featureView) => { + data?.spec?.features?.forEach((featureView) => { numFeatureViews += 1; - numFeatures += featureView.featureColumns.length; + numFeatures += featureView?.featureColumns!.length; }); } @@ -66,10 +67,10 @@ const FeatureServiceOverviewTab = () => { description="Feature Views" /> - {data.meta.lastUpdatedTimestamp ? ( + {data?.meta?.lastUpdatedTimestamp ? ( {

Features

- {data.spec.features ? ( - + {data?.spec?.features ? ( + ) : ( No features specified for this feature service. @@ -103,7 +104,7 @@ const FeatureServiceOverviewTab = () => {

Tags

- {data.spec.tags ? ( + {data?.spec?.tags ? ( { @@ -154,11 +155,11 @@ const FeatureServiceOverviewTab = () => {

All Feature Views

- {data.spec.features.length > 0 ? ( + {data?.spec?.features?.length! > 0 ? ( { - return f.featureViewName; - })} + fvNames={data?.spec?.features?.map((f) => { + return f.featureViewName!; + })!} /> ) : ( No feature views in this feature service diff --git a/ui/src/pages/feature-services/Index.tsx b/ui/src/pages/feature-services/Index.tsx index 441f3cf82c9..d034a24bd7c 100644 --- a/ui/src/pages/feature-services/Index.tsx +++ b/ui/src/pages/feature-services/Index.tsx @@ -22,12 +22,12 @@ import { filterInputInterface, tagTokenGroupsType, } from "../../hooks/useSearchInputWithTags"; -import { FeastFeatureServiceType } from "../../parsers/feastFeatureServices"; import { useDocumentTitle } from "../../hooks/useDocumentTitle"; import RegistryPathContext from "../../contexts/RegistryPathContext"; import FeatureServiceIndexEmptyState from "./FeatureServiceIndexEmptyState"; import TagSearch from "../../components/TagSearch"; import { useFeatureServiceTagsAggregation } from "../../hooks/useTagsAggregation"; +import { feast } from "../../protos"; const useLoadFeatureServices = () => { const registryUrl = useContext(RegistryPathContext); @@ -45,11 +45,11 @@ const useLoadFeatureServices = () => { }; const shouldIncludeFSsGivenTokenGroups = ( - entry: FeastFeatureServiceType, + entry: feast.core.IFeatureService, tagTokenGroups: tagTokenGroupsType ) => { return Object.entries(tagTokenGroups).every(([key, values]) => { - const entryTagValue = entry.spec.tags ? entry.spec.tags[key] : undefined; + const entryTagValue = entry?.spec?.tags ? entry.spec.tags[key] : undefined; if (entryTagValue) { return values.every((value) => { @@ -62,7 +62,7 @@ const shouldIncludeFSsGivenTokenGroups = ( }; const filterFn = ( - data: FeastFeatureServiceType[], + data: feast.core.IFeatureService[], filterInput: filterInputInterface ) => { let filteredByTags = data; @@ -79,7 +79,7 @@ const filterFn = ( if (filterInput.searchTokens.length) { return filteredByTags.filter((entry) => { return filterInput.searchTokens.find((token) => { - return token.length >= 3 && entry.spec.name.indexOf(token) >= 0; + return token.length >= 3 && entry?.spec?.name?.indexOf(token)! >= 0; }); }); } diff --git a/ui/src/pages/feature-services/useLoadFeatureService.ts b/ui/src/pages/feature-services/useLoadFeatureService.ts index be2242eae04..7d991533a1d 100644 --- a/ui/src/pages/feature-services/useLoadFeatureService.ts +++ b/ui/src/pages/feature-services/useLoadFeatureService.ts @@ -13,23 +13,23 @@ const useLoadFeatureService = (featureServiceName: string) => { registryQuery.data === undefined ? undefined : registryQuery.data.objects.featureServices?.find( - (fs) => fs.spec.name === featureServiceName - ); + (fs) => fs?.spec?.name === featureServiceName + ); let entities = data === undefined ? undefined : registryQuery.data?.indirectRelationships - .filter((relationship) => { - return ( - relationship.target.type === FEAST_FCO_TYPES.featureService && - relationship.target.name === data.spec.name && - relationship.source.type === FEAST_FCO_TYPES.entity - ); - }) - .map((relationship) => { - return relationship.source; - }); + .filter((relationship) => { + return ( + relationship.target.type === FEAST_FCO_TYPES.featureService && + relationship.target.name === data?.spec?.name && + relationship.source.type === FEAST_FCO_TYPES.entity + ); + }) + .map((relationship) => { + return relationship.source; + }); // Deduplicate on name of entity if (entities) { let entityToName: { [key: string]: EntityReference } = {}; diff --git a/ui/src/pages/feature-views/FeatureViewInstance.tsx b/ui/src/pages/feature-views/FeatureViewInstance.tsx index 5352507573f..dbe4dad6ec1 100644 --- a/ui/src/pages/feature-views/FeatureViewInstance.tsx +++ b/ui/src/pages/feature-views/FeatureViewInstance.tsx @@ -3,15 +3,13 @@ import React from "react"; import { useParams } from "react-router-dom"; import { EuiLoadingSpinner } from "@elastic/eui"; -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"; - +import { feast } from "../../protos"; const FeatureViewInstance = () => { const { featureViewName } = useParams(); @@ -38,18 +36,18 @@ const FeatureViewInstance = () => { if (isSuccess && !isEmpty) { if (data.type === FEAST_FV_TYPES.regular) { - const fv: FeastFeatureViewType = data.object; + const fv: feast.core.IFeatureView = data.object; return ; } if (data.type === FEAST_FV_TYPES.ondemand) { - const odfv: FeastODFVType = data.object; + const odfv: feast.core.IOnDemandFeatureView = data.object; return ; } if (data.type === FEAST_FV_TYPES.stream) { - const sfv: FeastSFVType = data.object; + const sfv: feast.core.IStreamFeatureView = data.object; return ; } diff --git a/ui/src/pages/feature-views/FeatureViewListingTable.tsx b/ui/src/pages/feature-views/FeatureViewListingTable.tsx index ceb756db804..e4eccecc975 100644 --- a/ui/src/pages/feature-views/FeatureViewListingTable.tsx +++ b/ui/src/pages/feature-views/FeatureViewListingTable.tsx @@ -58,7 +58,7 @@ const FeatureViewListingTable = ({ let tag = n/a; if (item.type === "regular") { - const value = item.object.spec.tags + const value = item?.object?.spec!.tags ? item.object.spec.tags[key] : undefined; diff --git a/ui/src/pages/feature-views/FeatureViewSummaryStatisticsTab.tsx b/ui/src/pages/feature-views/FeatureViewSummaryStatisticsTab.tsx deleted file mode 100644 index 7371d4a73bf..00000000000 --- a/ui/src/pages/feature-views/FeatureViewSummaryStatisticsTab.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React from "react"; - -import { EuiEmptyPrompt, EuiLoadingContent, EuiTitle } from "@elastic/eui"; -import { useParams } from "react-router-dom"; -import useLoadFeatureViewSummaryStatistics from "../../queries/useLoadFeatureViewSummaryStatistics"; -import { - NumericColumnSummaryStatisticType, - StringColumnSummaryStatisticType, -} from "../../parsers/featureViewSummaryStatistics"; -import NumericFeaturesTable from "../../components/NumericFeaturesTable"; - -interface ColumnsByGroup { - INT64?: NumericColumnSummaryStatisticType[]; - STRING?: StringColumnSummaryStatisticType[]; -} - -const FeatureViewSummaryStatisticsTab = () => { - let { featureViewName } = useParams(); - - if (!featureViewName) { - throw new Error("Unable to get Feature View Name"); - } - - const { isError, data } = - useLoadFeatureViewSummaryStatistics(featureViewName); - - if (isError) { - return ( - Error loading Statistics} - body={ -

- There was an error loading statistics for{" "} - {featureViewName}. Please check that statistics - have been generated. -

- } - /> - ); - } - - if (data) { - const columnsByGroup = Object.entries( - data.columnsSummaryStatistics - ).reduce((memo, [key, columnStatistics]) => { - if (columnStatistics.valueType === "INT64") { - if (!memo["INT64"]) { - memo[columnStatistics.valueType] = [columnStatistics]; - } else { - memo["INT64"].push(columnStatistics); - } - } - - if (columnStatistics.valueType === "STRING") { - if (!memo["STRING"]) { - memo[columnStatistics.valueType] = [columnStatistics]; - } else { - memo["STRING"].push(columnStatistics); - } - } - - return memo; - }, {}); - - return ( - - {columnsByGroup["INT64"] && ( - <> - -

Numeric Columns

-
- - - )} -
- ); - } - - return ; -}; - -export default FeatureViewSummaryStatisticsTab; diff --git a/ui/src/pages/feature-views/Index.tsx b/ui/src/pages/feature-views/Index.tsx index 3abd42a22b2..b874a532362 100644 --- a/ui/src/pages/feature-views/Index.tsx +++ b/ui/src/pages/feature-views/Index.tsx @@ -48,7 +48,7 @@ const shouldIncludeFVsGivenTokenGroups = ( tagTokenGroups: Record ) => { return Object.entries(tagTokenGroups).every(([key, values]) => { - const entryTagValue = entry.object.spec.tags + const entryTagValue = entry?.object?.spec!.tags ? entry.object.spec.tags[key] : undefined; diff --git a/ui/src/pages/feature-views/OnDemandFeatureViewInstance.tsx b/ui/src/pages/feature-views/OnDemandFeatureViewInstance.tsx index 1db2f5194fd..166746df220 100644 --- a/ui/src/pages/feature-views/OnDemandFeatureViewInstance.tsx +++ b/ui/src/pages/feature-views/OnDemandFeatureViewInstance.tsx @@ -9,16 +9,16 @@ import { import { FeatureViewIcon32 } from "../../graphics/FeatureViewIcon"; import { useMatchExact } from "../../hooks/useMatchSubpath"; -import { FeastODFVType } from "../../parsers/feastODFVS"; import OnDemandFeatureViewOverviewTab from "./OnDemandFeatureViewOverviewTab"; import { useOnDemandFeatureViewCustomTabs, useOnDemandFeatureViewCustomTabRoutes, } from "../../custom-tabs/TabsRegistryContext"; +import { feast } from "../../protos"; interface OnDemandFeatureInstanceProps { - data: FeastODFVType; + data: feast.core.IOnDemandFeatureView; } const OnDemandFeatureInstance = ({ data }: OnDemandFeatureInstanceProps) => { diff --git a/ui/src/pages/feature-views/OnDemandFeatureViewOverviewTab.tsx b/ui/src/pages/feature-views/OnDemandFeatureViewOverviewTab.tsx index 0922f62102b..ee8e41bbf6c 100644 --- a/ui/src/pages/feature-views/OnDemandFeatureViewOverviewTab.tsx +++ b/ui/src/pages/feature-views/OnDemandFeatureViewOverviewTab.tsx @@ -10,11 +10,6 @@ import { } from "@elastic/eui"; import React from "react"; import FeaturesListDisplay from "../../components/FeaturesListDisplay"; -import { - FeastODFVType, - RequestDataSourceType, - FeatureViewProjectionType, -} from "../../parsers/feastODFVS"; import { useParams } from "react-router-dom"; import { EntityRelation } from "../../parsers/parseEntityRelationships"; import { FEAST_FCO_TYPES } from "../../parsers/types"; @@ -22,9 +17,10 @@ import useLoadRelationshipData from "../../queries/useLoadRelationshipsData"; import FeatureViewProjectionDisplayPanel from "./components/FeatureViewProjectionDisplayPanel"; import RequestDataDisplayPanel from "./components/RequestDataDisplayPanel"; import ConsumingFeatureServicesList from "./ConsumingFeatureServicesList"; +import { feast } from "../../protos"; interface OnDemandFeatureViewOverviewTabProps { - data: FeastODFVType; + data: feast.core.IOnDemandFeatureView; } const whereFSconsumesThisFv = (fvName: string) => { @@ -39,13 +35,13 @@ const whereFSconsumesThisFv = (fvName: string) => { const OnDemandFeatureViewOverviewTab = ({ data, }: OnDemandFeatureViewOverviewTabProps) => { - const inputs = Object.entries(data.spec.sources); + const inputs = Object.entries(data?.spec?.sources!); const { projectName } = useParams(); const relationshipQuery = useLoadRelationshipData(); const fsNames = relationshipQuery.data ? relationshipQuery.data - .filter(whereFSconsumesThisFv(data.spec.name)) + .filter(whereFSconsumesThisFv(data?.spec?.name!)) .map((fs) => { return fs.target.name; }) @@ -61,7 +57,7 @@ const OnDemandFeatureViewOverviewTab = ({ - {data.spec.userDefinedFunction.body} + {data?.spec?.userDefinedFunction?.bodyText}
@@ -70,13 +66,13 @@ const OnDemandFeatureViewOverviewTab = ({ -

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

+

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

- {projectName && data.spec.features ? ( + {projectName && data?.spec?.features ? ( @@ -93,21 +89,26 @@ const OnDemandFeatureViewOverviewTab = ({ {inputs.map(([key, inputGroup]) => { - if ((inputGroup as RequestDataSourceType).requestDataSource) { + if ( + (inputGroup as feast.core.IOnDemandSource).requestDataSource + ) { return ( ); } - if (inputGroup as FeatureViewProjectionType) { + if ( + (inputGroup as feast.core.IOnDemandSource) + .featureViewProjection + ) { return ( ); diff --git a/ui/src/pages/feature-views/RegularFeatureViewInstance.tsx b/ui/src/pages/feature-views/RegularFeatureViewInstance.tsx index 7200163614b..6d34745d7cb 100644 --- a/ui/src/pages/feature-views/RegularFeatureViewInstance.tsx +++ b/ui/src/pages/feature-views/RegularFeatureViewInstance.tsx @@ -9,18 +9,17 @@ import { import { FeatureViewIcon32 } from "../../graphics/FeatureViewIcon"; import { useMatchExact, useMatchSubpath } from "../../hooks/useMatchSubpath"; -import { FeastFeatureViewType } from "../../parsers/feastFeatureViews"; import RegularFeatureViewOverviewTab from "./RegularFeatureViewOverviewTab"; -import FeatureViewSummaryStatisticsTab from "./FeatureViewSummaryStatisticsTab"; import { useRegularFeatureViewCustomTabs, useRegularFeatureViewCustomTabRoutes, } from "../../custom-tabs/TabsRegistryContext"; import FeatureFlagsContext from "../../contexts/FeatureFlagsContext"; +import { feast } from "../../protos"; interface RegularFeatureInstanceProps { - data: FeastFeatureViewType; + data: feast.core.IFeatureView; } const RegularFeatureInstance = ({ data }: RegularFeatureInstanceProps) => { @@ -58,7 +57,7 @@ const RegularFeatureInstance = ({ data }: RegularFeatureInstanceProps) => { { path="/" element={} /> - } - /> {TabRoutes} diff --git a/ui/src/pages/feature-views/RegularFeatureViewOverviewTab.tsx b/ui/src/pages/feature-views/RegularFeatureViewOverviewTab.tsx index 689bc6b9024..3bbb906e05b 100644 --- a/ui/src/pages/feature-views/RegularFeatureViewOverviewTab.tsx +++ b/ui/src/pages/feature-views/RegularFeatureViewOverviewTab.tsx @@ -15,12 +15,13 @@ import { useNavigate, useParams } from "react-router-dom"; import FeaturesListDisplay from "../../components/FeaturesListDisplay"; import TagsDisplay from "../../components/TagsDisplay"; import { encodeSearchQueryString } from "../../hooks/encodeSearchQueryString"; -import { FeastFeatureViewType } from "../../parsers/feastFeatureViews"; import { EntityRelation } from "../../parsers/parseEntityRelationships"; import { FEAST_FCO_TYPES } from "../../parsers/types"; import useLoadRelationshipData from "../../queries/useLoadRelationshipsData"; import BatchSourcePropertiesView from "../data-sources/BatchSourcePropertiesView"; import ConsumingFeatureServicesList from "./ConsumingFeatureServicesList"; +import { feast } from "../../protos"; +import { toDate } from "../../utils/timestamp"; const whereFSconsumesThisFv = (fvName: string) => { return (r: EntityRelation) => { @@ -32,7 +33,7 @@ const whereFSconsumesThisFv = (fvName: string) => { }; interface RegularFeatureViewOverviewTabProps { - data: FeastFeatureViewType; + data: feast.core.IFeatureView; } const RegularFeatureViewOverviewTab = ({ @@ -49,8 +50,8 @@ const RegularFeatureViewOverviewTab = ({ const fsNames = relationshipQuery.data ? relationshipQuery.data.filter(whereFSconsumesThisFv(fvName)).map((fs) => { - return fs.target.name; - }) + return fs.target.name; + }) : []; const numOfFs = fsNames.length; @@ -66,13 +67,13 @@ const RegularFeatureViewOverviewTab = ({ -

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

+

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

- {projectName && data.spec.features ? ( + {projectName && data?.spec?.features ? ( @@ -87,7 +88,7 @@ const RegularFeatureViewOverviewTab = ({

Entities

- {data.spec.entities ? ( + {data?.spec?.entities ? ( {data.spec.entities.map((entity) => { return ( @@ -128,7 +129,7 @@ const RegularFeatureViewOverviewTab = ({

Tags

- {data.spec.tags ? ( + {data?.spec?.tags ? ( { @@ -137,8 +138,8 @@ const RegularFeatureViewOverviewTab = ({ encodeSearchQueryString(`${key}:${value}`) ); }} - owner={data.spec.owner} - description={data.spec.description} + owner={data?.spec?.owner!} + description={data?.spec?.description!} /> ) : ( No Tags specified on this feature view. @@ -154,7 +155,7 @@ const RegularFeatureViewOverviewTab = ({

Batch Source

- +
@@ -164,11 +165,11 @@ const RegularFeatureViewOverviewTab = ({

Materialization Intervals

- {data.meta.materializationIntervals?.map((interval, i) => { + {data?.meta?.materializationIntervals?.map((interval, i) => { return (

- {interval.startTime.toLocaleDateString("en-CA")} to{" "} - {interval.endTime.toLocaleDateString("en-CA")} + {toDate(interval.startTime!).toLocaleDateString("en-CA")} to{" "} + {toDate(interval.endTime!).toLocaleDateString("en-CA")}

); })} diff --git a/ui/src/pages/feature-views/StreamFeatureViewInstance.tsx b/ui/src/pages/feature-views/StreamFeatureViewInstance.tsx index ba4c0087278..52bc06bc5e1 100644 --- a/ui/src/pages/feature-views/StreamFeatureViewInstance.tsx +++ b/ui/src/pages/feature-views/StreamFeatureViewInstance.tsx @@ -9,16 +9,16 @@ import { import { FeatureViewIcon32 } from "../../graphics/FeatureViewIcon"; import { useMatchExact } from "../../hooks/useMatchSubpath"; -import { FeastSFVType } from "../../parsers/feastSFVS"; import StreamFeatureViewOverviewTab from "./StreamFeatureViewOverviewTab"; import { useStreamFeatureViewCustomTabs, useStreamFeatureViewCustomTabRoutes, } from "../../custom-tabs/TabsRegistryContext"; +import { feast } from "../../protos"; interface StreamFeatureInstanceProps { - data: FeastSFVType; + data: feast.core.IStreamFeatureView; } const StreamFeatureInstance = ({ data }: StreamFeatureInstanceProps) => { diff --git a/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx b/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx index 56efc428453..b6cf12d3725 100644 --- a/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx +++ b/ui/src/pages/feature-views/StreamFeatureViewOverviewTab.tsx @@ -10,18 +10,16 @@ import { } from "@elastic/eui"; import React from "react"; import FeaturesListDisplay from "../../components/FeaturesListDisplay"; -import { - FeastSFVType, -} 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 ConsumingFeatureServicesList from "./ConsumingFeatureServicesList"; import EuiCustomLink from "../../components/EuiCustomLink"; +import { feast } from "../../protos"; interface StreamFeatureViewOverviewTabProps { - data: FeastSFVType; + data: feast.core.IStreamFeatureView; } const whereFSconsumesThisFv = (fvName: string) => { @@ -36,13 +34,13 @@ const whereFSconsumesThisFv = (fvName: string) => { const StreamFeatureViewOverviewTab = ({ data, }: StreamFeatureViewOverviewTabProps) => { - const inputs = Object.entries([data.spec.streamSource]); + const inputs = Object.entries([data.spec?.streamSource]); const { projectName } = useParams(); const relationshipQuery = useLoadRelationshipData(); const fsNames = relationshipQuery.data ? relationshipQuery.data - .filter(whereFSconsumesThisFv(data.spec.name)) + .filter(whereFSconsumesThisFv(data.spec?.name!)) .map((fs) => { return fs.target.name; }) @@ -58,7 +56,7 @@ const StreamFeatureViewOverviewTab = ({ - {data.spec.userDefinedFunction.body} + {data.spec?.userDefinedFunction?.body}
@@ -67,13 +65,13 @@ const StreamFeatureViewOverviewTab = ({ -

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

+

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

- {projectName && data.spec.features ? ( + {projectName && data.spec?.features ? ( @@ -98,10 +96,10 @@ const StreamFeatureViewOverviewTab = ({ - {inputGroup.name} + {inputGroup?.name} diff --git a/ui/src/pages/feature-views/components/FeatureViewProjectionDisplayPanel.tsx b/ui/src/pages/feature-views/components/FeatureViewProjectionDisplayPanel.tsx index 7b110f326d7..156f6db1ec6 100644 --- a/ui/src/pages/feature-views/components/FeatureViewProjectionDisplayPanel.tsx +++ b/ui/src/pages/feature-views/components/FeatureViewProjectionDisplayPanel.tsx @@ -1,25 +1,26 @@ import React from "react"; import { EuiBasicTable, EuiPanel, EuiText, EuiTitle } from "@elastic/eui"; -import { FeatureViewProjectionType } from "../../../parsers/feastODFVS"; import { useParams } from "react-router-dom"; import EuiCustomLink from "../../../components/EuiCustomLink"; +import { feast } from "../../../protos"; -interface RequestDataDisplayPanelProps extends FeatureViewProjectionType {} +interface RequestDataDisplayPanelProps extends feast.core.IFeatureViewProjection { } -const FeatureViewProjectionDisplayPanel = ({ - featureViewProjection, -}: RequestDataDisplayPanelProps) => { +const FeatureViewProjectionDisplayPanel = (featureViewProjection: RequestDataDisplayPanelProps) => { const { projectName } = useParams(); const columns = [ { name: "Column Name", - field: "name", + field: "name" }, { name: "Type", field: "valueType", + render: (valueType: any) => { + return feast.types.ValueType.Enum[valueType]; + }, }, ]; @@ -33,12 +34,12 @@ const FeatureViewProjectionDisplayPanel = ({ href={`/p/${projectName}/feature-view/${featureViewProjection.featureViewName}`} to={`/p/${projectName}/feature-view/${featureViewProjection.featureViewName}`} > - {featureViewProjection.featureViewName} + {featureViewProjection?.featureViewName}
); diff --git a/ui/src/pages/feature-views/components/RequestDataDisplayPanel.tsx b/ui/src/pages/feature-views/components/RequestDataDisplayPanel.tsx index a6e546d9d89..e8e6854389a 100644 --- a/ui/src/pages/feature-views/components/RequestDataDisplayPanel.tsx +++ b/ui/src/pages/feature-views/components/RequestDataDisplayPanel.tsx @@ -1,17 +1,17 @@ import React from "react"; import { EuiBasicTable, EuiPanel, EuiText, EuiTitle } from "@elastic/eui"; import { useParams } from "react-router-dom"; -import { RequestDataSourceType } from "../../../parsers/feastODFVS"; import EuiCustomLink from "../../../components/EuiCustomLink"; +import { feast } from "../../../protos"; -interface RequestDataDisplayPanelProps extends RequestDataSourceType {} +interface RequestDataDisplayPanelProps extends feast.core.IOnDemandSource { } const RequestDataDisplayPanel = ({ requestDataSource, }: RequestDataDisplayPanelProps) => { const { projectName } = useParams(); - const items = Object.entries(requestDataSource.requestDataOptions.schema).map( + const items = Object.entries(requestDataSource?.requestDataOptions?.schema!).map( ([key, type]) => { return { key, @@ -38,10 +38,10 @@ const RequestDataDisplayPanel = ({ - {requestDataSource.name} + {requestDataSource?.name} diff --git a/ui/src/pages/feature-views/useLoadFeatureView.ts b/ui/src/pages/feature-views/useLoadFeatureView.ts index 7685171b72b..14970360f24 100644 --- a/ui/src/pages/feature-views/useLoadFeatureView.ts +++ b/ui/src/pages/feature-views/useLoadFeatureView.ts @@ -25,8 +25,8 @@ const useLoadRegularFeatureView = (featureViewName: string) => { registryQuery.data === undefined ? undefined : registryQuery.data.objects.featureViews?.find((fv) => { - return fv.spec.name === featureViewName; - }); + return fv?.spec?.name === featureViewName; + }); return { ...registryQuery, @@ -42,8 +42,8 @@ const useLoadOnDemandFeatureView = (featureViewName: string) => { registryQuery.data === undefined ? undefined : registryQuery.data.objects.onDemandFeatureViews?.find((fv) => { - return fv.spec.name === featureViewName; - }); + return fv?.spec?.name === featureViewName; + }); return { ...registryQuery, @@ -59,7 +59,7 @@ const useLoadStreamFeatureView = (featureViewName: string) => { registryQuery.data === undefined ? undefined : registryQuery.data.objects.streamFeatureViews?.find((fv) => { - return fv.spec.name === featureViewName; + return fv.spec?.name === featureViewName; }); return { diff --git a/ui/src/pages/features/FeatureOverviewTab.tsx b/ui/src/pages/features/FeatureOverviewTab.tsx index 0a1c48509c2..09da62a87a8 100644 --- a/ui/src/pages/features/FeatureOverviewTab.tsx +++ b/ui/src/pages/features/FeatureOverviewTab.tsx @@ -13,6 +13,7 @@ import EuiCustomLink from "../../components/EuiCustomLink"; import React from "react"; import { useParams } from "react-router-dom"; import useLoadFeature from "./useLoadFeature"; +import { feast } from "../../protos"; const FeatureOverviewTab = () => { let { projectName, FeatureViewName, FeatureName } = useParams(); @@ -48,7 +49,7 @@ const FeatureOverviewTab = () => { Value Type - {featureData?.valueType} + {feast.types.ValueType.Enum[featureData?.valueType!]} FeatureView diff --git a/ui/src/pages/features/useLoadFeature.ts b/ui/src/pages/features/useLoadFeature.ts index 5ddaf282043..f1918dd4d59 100644 --- a/ui/src/pages/features/useLoadFeature.ts +++ b/ui/src/pages/features/useLoadFeature.ts @@ -10,15 +10,15 @@ const useLoadFeature = (featureViewName: string, featureName: string) => { registryQuery.data === undefined ? undefined : registryQuery.data.objects.featureViews?.find((fv) => { - return fv.spec.name === featureViewName; - }); + return fv?.spec?.name === featureViewName; + }); - const featureData = + const featureData = data === undefined ? undefined - : data?.spec.features.find((f) => { - return f.name === featureName; - }); + : data?.spec?.features?.find((f) => { + return f.name === featureName; + }); return { ...registryQuery, diff --git a/ui/src/pages/saved-data-sets/DatasetExpectationsTab.tsx b/ui/src/pages/saved-data-sets/DatasetExpectationsTab.tsx index 10ebb87297b..dc49355c28f 100644 --- a/ui/src/pages/saved-data-sets/DatasetExpectationsTab.tsx +++ b/ui/src/pages/saved-data-sets/DatasetExpectationsTab.tsx @@ -11,7 +11,7 @@ const DatasetExpectationsTab = () => { } const { isSuccess, data } = useLoadDataset(datasetName); - if (!data || !data.spec.profile) { + if (!data || !data.spec?.name) { return ( No data so sad @@ -21,15 +21,9 @@ const DatasetExpectationsTab = () => { let expectationsData; - try { - expectationsData = JSON.parse(data.spec.profile); - } catch (e) { - throw new Error(`Unable to parse Expectations Profile: ${e}`); - } - - return isSuccess && expectationsData ? ( + return isSuccess ? ( -
{JSON.stringify(expectationsData, null, 2)}
+
{JSON.stringify(data.spec, null, 2)}
) : ( diff --git a/ui/src/pages/saved-data-sets/DatasetOverviewTab.tsx b/ui/src/pages/saved-data-sets/DatasetOverviewTab.tsx index a20c83b1e21..5f6ffa2101c 100644 --- a/ui/src/pages/saved-data-sets/DatasetOverviewTab.tsx +++ b/ui/src/pages/saved-data-sets/DatasetOverviewTab.tsx @@ -15,6 +15,7 @@ import { useParams } from "react-router-dom"; import DatasetFeaturesTable from "./DatasetFeaturesTable"; import DatasetJoinKeysTable from "./DatasetJoinKeysTable"; import useLoadDataset from "./useLoadDataset"; +import { toDate } from "../../utils/timestamp"; const EntityOverviewTab = () => { let { datasetName } = useParams(); @@ -47,7 +48,7 @@ const EntityOverviewTab = () => { { + features={data.spec?.features!.map((joinedName: string) => { const [featureViewName, featureName] = joinedName.split(":"); @@ -55,7 +56,7 @@ const EntityOverviewTab = () => { featureViewName, featureName, }; - })} + })!} /> @@ -65,9 +66,9 @@ const EntityOverviewTab = () => { { + joinKeys={data?.spec?.joinKeys!.map((joinKey) => { return { name: joinKey }; - })} + })!} />
@@ -82,7 +83,7 @@ const EntityOverviewTab = () => { Source Feature Service - {data.spec.featureService} + {data?.spec?.featureServiceName!} @@ -91,7 +92,7 @@ const EntityOverviewTab = () => { Created - {data.meta.createdTimestamp.toLocaleDateString("en-CA")} + {toDate(data?.meta?.createdTimestamp!).toLocaleDateString("en-CA")} diff --git a/ui/src/pages/saved-data-sets/DatasetsListingTable.tsx b/ui/src/pages/saved-data-sets/DatasetsListingTable.tsx index 97d11b0b24c..a1a97084171 100644 --- a/ui/src/pages/saved-data-sets/DatasetsListingTable.tsx +++ b/ui/src/pages/saved-data-sets/DatasetsListingTable.tsx @@ -2,10 +2,11 @@ import React from "react"; import { EuiBasicTable } from "@elastic/eui"; import EuiCustomLink from "../../components/EuiCustomLink"; import { useParams } from "react-router-dom"; -import { FeastSavedDatasetType } from "../../parsers/feastSavedDataset"; +import { feast } from "../../protos"; +import { toDate } from "../../utils/timestamp"; interface DatasetsListingTableProps { - datasets: FeastSavedDatasetType[]; + datasets: feast.core.ISavedDataset[]; } const DatasetsListingTable = ({ datasets }: DatasetsListingTableProps) => { @@ -33,15 +34,15 @@ const DatasetsListingTable = ({ datasets }: DatasetsListingTableProps) => { }, { name: "Created", - render: (item: FeastSavedDatasetType) => { - return item.meta.createdTimestamp.toLocaleDateString("en-CA"); + render: (item: feast.core.ISavedDataset) => { + return toDate(item?.meta?.createdTimestamp!).toLocaleString("en-CA")!; }, }, ]; - const getRowProps = (item: FeastSavedDatasetType) => { + const getRowProps = (item: feast.core.ISavedDataset) => { return { - "data-test-subj": `row-${item.spec.name}`, + "data-test-subj": `row-${item.spec?.name}`, }; }; diff --git a/ui/src/pages/saved-data-sets/useLoadDataset.ts b/ui/src/pages/saved-data-sets/useLoadDataset.ts index a3dbd3225d8..17a77d97993 100644 --- a/ui/src/pages/saved-data-sets/useLoadDataset.ts +++ b/ui/src/pages/saved-data-sets/useLoadDataset.ts @@ -10,8 +10,8 @@ const useLoadEntity = (entityName: string) => { registryQuery.data === undefined ? undefined : registryQuery.data.objects.savedDatasets?.find( - (fv) => fv.spec.name === entityName - ); + (fv) => fv.spec?.name === entityName + ); return { ...registryQuery, diff --git a/ui/src/parsers/feastDatasources.ts b/ui/src/parsers/feastDatasources.ts deleted file mode 100644 index 3e1dca72d1f..00000000000 --- a/ui/src/parsers/feastDatasources.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { z } from "zod"; -import { FeastFeatureColumnSchema } from "./feastFeatureViews"; - -const FeastDatasourceSchema = z.object({ - type: z.string(), - eventTimestampColumn: z.string().optional(), - createdTimestampColumn: z.string().optional(), - fileOptions: z.object({ - uri: z.string().optional(), - }).optional(), - name: z.string(), - description: z.string().optional(), - owner: z.string().optional(), - meta: z.object({ - latestEventTimestamp: z.string().transform((val) => new Date(val)), - earliestEventTimestamp: z.string().transform((val) => new Date(val)), - }).optional(), - requestDataOptions: z.object({ - schema: z.array(FeastFeatureColumnSchema), - }).optional(), - bigqueryOptions: z.object({ - tableRef: z.string().optional(), - dbtModelSerialized: z.string().optional() - }).optional(), -}); - -type FeastDatasourceType = z.infer; - -export { FeastDatasourceSchema }; -export type { FeastDatasourceType }; diff --git a/ui/src/parsers/feastEntities.ts b/ui/src/parsers/feastEntities.ts deleted file mode 100644 index 09057c6fe92..00000000000 --- a/ui/src/parsers/feastEntities.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { z } from "zod"; -import { FEAST_FEATURE_VALUE_TYPES } from "./types"; - -const FeastEntitySchema = z.object({ - spec: z.object({ - name: z.string(), - valueType: z.nativeEnum(FEAST_FEATURE_VALUE_TYPES).optional(), - joinKey: z.string(), - description: z.string().optional(), - labels: z.record(z.string()).optional(), - }), - meta: z.object({ - createdTimestamp: z.string().transform((val) => new Date(val)).optional(), - lastUpdatedTimestamp: z.string().transform((val) => new Date(val)).optional(), - }), -}); - -type FeastEntityType = z.infer; - -export { FeastEntitySchema }; -export type { FeastEntityType }; diff --git a/ui/src/parsers/feastFeatureServices.ts b/ui/src/parsers/feastFeatureServices.ts deleted file mode 100644 index 6812b7e02cb..00000000000 --- a/ui/src/parsers/feastFeatureServices.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { z } from "zod"; -import { FEAST_FEATURE_VALUE_TYPES } from "./types"; - -const FeatureColumnInService = z.object({ - name: z.string(), - valueType: z.nativeEnum(FEAST_FEATURE_VALUE_TYPES), -}); - -const FeatureInServiceSchema = z.object({ - featureViewName: z.string(), - featureColumns: z.array(FeatureColumnInService), -}); - -const FeastFeatureServiceSchema = z.object({ - spec: z.object({ - name: z.string(), - features: z.array(FeatureInServiceSchema), - tags: z.record(z.string()).optional(), - description: z.string().optional(), - }), - meta: z.object({ - createdTimestamp: z.string().transform((val) => new Date(val)).optional(), - lastUpdatedTimestamp: z.string().transform((val) => new Date(val)).optional(), - }), -}); - -type FeastFeatureServiceType = z.infer; -type FeastFeatureInServiceType = z.infer; - -export { FeastFeatureServiceSchema }; -export type { FeastFeatureServiceType, FeastFeatureInServiceType }; diff --git a/ui/src/parsers/feastFeatureViews.ts b/ui/src/parsers/feastFeatureViews.ts deleted file mode 100644 index cbf15d280e6..00000000000 --- a/ui/src/parsers/feastFeatureViews.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { z } from "zod"; -import { FEAST_FEATURE_VALUE_TYPES } from "./types"; - -const FeastFeatureColumnSchema = z.object({ - name: z.string(), - valueType: z.nativeEnum(FEAST_FEATURE_VALUE_TYPES), - tags: z.record(z.string()).optional(), -}); - -const FeastBatchSourceSchema = z.object({ - type: z.string(), - eventTimestampColumn: z.string().optional(), - createdTimestampColumn: z.string().optional(), - fileOptions: z.object({ - uri: z.string().optional(), - }).optional(), - name: z.string().optional(), - description: z.string().optional(), - owner: z.string().optional(), - meta: z.object({ - earliestEventTimestamp: z.string().transform((val) => new Date(val)), - latestEventTimestamp: z.string().transform((val) => new Date(val)), - }).optional(), - requestDataOptions: z.object({ - schema: z.record(z.nativeEnum(FEAST_FEATURE_VALUE_TYPES)), - }).optional(), - bigqueryOptions: z.object({ - tableRef: z.string().optional(), - dbtModelSerialized: z.string().optional() - }).optional(), - dataSourceClassType: z.string(), -}); - -const FeastFeatureViewSchema = z.object({ - spec: z.object({ - description: z.string().optional(), - name: z.string(), - entities: z.array(z.string()), - features: z.array(FeastFeatureColumnSchema), - ttl: z.string().transform((val) => parseInt(val)), - batchSource: FeastBatchSourceSchema, - online: z.boolean().optional(), - owner: z.string().optional(), - tags: z.record(z.string()).optional(), - }), - meta: z.object({ - createdTimestamp: z.string().transform((val) => new Date(val)).optional(), - lastUpdatedTimestamp: z.string().transform((val) => new Date(val)).optional(), - materializationIntervals: z - .array( - z.object({ - startTime: z.string().transform((val) => new Date(val)), - endTime: z.string().transform((val) => new Date(val)), - }) - ) - .optional(), - }), -}); - -type FeastFeatureViewType = z.infer; -type FeastFeatureColumnType = z.infer; - -export { FeastFeatureViewSchema, FeastFeatureColumnSchema }; -export type { FeastFeatureViewType, FeastFeatureColumnType }; diff --git a/ui/src/parsers/feastFeatures.ts b/ui/src/parsers/feastFeatures.ts deleted file mode 100644 index 129120c168d..00000000000 --- a/ui/src/parsers/feastFeatures.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { z } from "zod"; -import { FEAST_FEATURE_VALUE_TYPES } from "./types"; -import { jsonSchema } from "./jsonType" - -const FeastFeatureSchema = z.object({ - name: z.string(), - valueType: z.nativeEnum(FEAST_FEATURE_VALUE_TYPES), - metadata: jsonSchema.optional(), -}); - -export { FeastFeatureSchema }; diff --git a/ui/src/parsers/feastODFVS.ts b/ui/src/parsers/feastODFVS.ts deleted file mode 100644 index 4d09cc72dfa..00000000000 --- a/ui/src/parsers/feastODFVS.ts +++ /dev/null @@ -1,47 +0,0 @@ -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 ODFVInputsSchema = z.union([ - FeatureViewProjectionSchema, - RequestDataSourceSchema, -]); - -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)), - }), -}); - -type FeastODFVType = z.infer; -type RequestDataSourceType = z.infer; -type FeatureViewProjectionType = z.infer; - -export { FeastODFVSchema }; -export type { FeastODFVType, RequestDataSourceType, FeatureViewProjectionType }; diff --git a/ui/src/parsers/feastRegistry.ts b/ui/src/parsers/feastRegistry.ts deleted file mode 100644 index f84187046a8..00000000000 --- a/ui/src/parsers/feastRegistry.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { z } from "zod"; -import { FeastDatasourceSchema } from "./feastDatasources"; -import { FeastEntitySchema } from "./feastEntities"; -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(), - dataSources: z.array(FeastDatasourceSchema).optional(), - 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(), -}); - -type FeastRegistryType = z.infer; - -export { FeastRegistrySchema }; -export type { FeastRegistryType }; diff --git a/ui/src/parsers/feastSFVS.ts b/ui/src/parsers/feastSFVS.ts deleted file mode 100644 index f21b3d1cdac..00000000000 --- a/ui/src/parsers/feastSFVS.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { z } from "zod"; -import { FeastFeatureColumnSchema } from "./feastFeatureViews"; -import {FeastDatasourceSchema} from "./feastDatasources"; - -const FeatureViewProjectionSchema = z.object({ - featureViewProjection: z.object({ - featureViewName: z.string(), - featureColumns: z.array(FeastFeatureColumnSchema), - }), -}); - -const StreamSourceSchema = z.object({ - type: z.string(), - name: z.string(), - owner: z.string().optional(), - description: z.string().optional(), -}); - -const FeastSFVSchema = z.object({ - spec: z.object({ - name: z.string(), - features: z.array(FeastFeatureColumnSchema), - batchSource: FeastDatasourceSchema, - streamSource: StreamSourceSchema, - 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 StreamSourceType = z.infer; -type FeatureViewProjectionType = z.infer; - -export { FeastSFVSchema }; -export type { FeastSFVType, StreamSourceType, FeatureViewProjectionType}; diff --git a/ui/src/parsers/feastSavedDataset.ts b/ui/src/parsers/feastSavedDataset.ts deleted file mode 100644 index ce1d39b4e73..00000000000 --- a/ui/src/parsers/feastSavedDataset.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { z } from "zod"; - -const FeastSavedDatasetSchema = z.object({ - spec: z.object({ - name: z.string(), - features: z.array(z.string()), - joinKeys: z.array(z.string()), - storage: z.object({ - fileStorage: z.object({ - fileFormat: z.object({ - parquetFormat: z.object({}).optional(), - }).optional(), - fileUrl: z.string(), - }).optional(), - }).optional(), - featureService: z - .object({ - spec: z.object({ - name: z.string(), - }), - }) - .transform((obj) => { - return obj.spec.name; - }).optional(), - profile: z.string().optional(), - }), - meta: z.object({ - createdTimestamp: z.string().transform((val) => new Date(val)), - minEventTimestamp: z.string().transform((val) => new Date(val)), - maxEventTimestamp: z.string().transform((val) => new Date(val)), - }), -}); - -type FeastSavedDatasetType = z.infer; - -export { FeastSavedDatasetSchema }; -export type { FeastSavedDatasetType }; diff --git a/ui/src/parsers/featureViewSummaryStatistics.ts b/ui/src/parsers/featureViewSummaryStatistics.ts deleted file mode 100644 index f8eca669d37..00000000000 --- a/ui/src/parsers/featureViewSummaryStatistics.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { z } from "zod"; - -const histogramSchema = z.array( - z.object({ - x0: z.number(), - x1: z.number(), - count: z.number(), - }) -); - -const numericColumnSummaryStaticsSchema = z.object({ - name: z.string(), - valueType: z.literal("INT64"), - sampleValues: z.array(z.number()), - histogram: histogramSchema.optional(), - proportionOfZeros: z.number().optional(), - proportionMissing: z.number().optional(), - min: z.number().optional(), - max: z.number().optional(), -}); - -const stringColumnSummaryStaticsSchema = z.object({ - name: z.string(), - valueType: z.literal("STRING"), - sampleValues: z.array(z.string()), -}); - -const columnsSummaryStatisticsSchema = z.union([ - numericColumnSummaryStaticsSchema, - stringColumnSummaryStaticsSchema, -]); - -const featureViewSummaryStatisticsSchema = z.object({ - columnsSummaryStatistics: z.record(columnsSummaryStatisticsSchema), -}); - -type FeatureViewSummaryStatisticsType = z.infer< - typeof featureViewSummaryStatisticsSchema ->; - -type NumericColumnSummaryStatisticType = z.infer< - typeof numericColumnSummaryStaticsSchema ->; -type StringColumnSummaryStatisticType = z.infer< - typeof stringColumnSummaryStaticsSchema ->; - -type HistogramDataType = z.infer; - -export { featureViewSummaryStatisticsSchema }; -export type { - FeatureViewSummaryStatisticsType, - HistogramDataType, - NumericColumnSummaryStatisticType, - StringColumnSummaryStatisticType, -}; 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/src/parsers/mergedFVTypes.ts b/ui/src/parsers/mergedFVTypes.ts index edf1adee9e5..7e23e095209 100644 --- a/ui/src/parsers/mergedFVTypes.ts +++ b/ui/src/parsers/mergedFVTypes.ts @@ -1,10 +1,4 @@ -import { - FeastFeatureColumnType, - FeastFeatureViewType, -} from "./feastFeatureViews"; -import { FeastODFVType } from "./feastODFVS"; -import { FeastSFVType } from "./feastSFVS"; -import { FeastRegistryType } from "./feastRegistry"; +import { feast } from "../protos"; enum FEAST_FV_TYPES { regular = "regular", @@ -15,64 +9,64 @@ enum FEAST_FV_TYPES { interface regularFVInterface { name: string; type: FEAST_FV_TYPES.regular; - features: FeastFeatureColumnType[]; - object: FeastFeatureViewType; + features: feast.core.IFeatureSpecV2[]; + object: feast.core.IFeatureView; } interface ODFVInterface { name: string; type: FEAST_FV_TYPES.ondemand; - features: FeastFeatureColumnType[]; - object: FeastODFVType; + features: feast.core.IOnDemandFeatureViewSpec[]; + object: feast.core.IOnDemandFeatureView; } interface SFVInterface { name: string; type: FEAST_FV_TYPES.stream; - features: FeastFeatureColumnType[]; - object: FeastSFVType; + features: feast.core.IFeatureSpecV2[]; + object: feast.core.IStreamFeatureView; } type genericFVType = regularFVInterface | ODFVInterface | SFVInterface; -const mergedFVTypes = (objects: FeastRegistryType) => { +const mergedFVTypes = (objects: feast.core.Registry) => { const mergedFVMap: Record = {}; const mergedFVList: genericFVType[] = []; objects.featureViews?.forEach((fv) => { const obj: genericFVType = { - name: fv.spec.name, + name: fv.spec?.name!, type: FEAST_FV_TYPES.regular, - features: fv.spec.features, + features: fv.spec?.features!, object: fv, }; - mergedFVMap[fv.spec.name] = obj; + mergedFVMap[fv.spec?.name!] = obj; mergedFVList.push(obj); }); objects.onDemandFeatureViews?.forEach((odfv) => { const obj: genericFVType = { - name: odfv.spec.name, + name: odfv.spec?.name!, type: FEAST_FV_TYPES.ondemand, - features: odfv.spec.features, + features: odfv.spec?.features!, object: odfv, }; - mergedFVMap[odfv.spec.name] = obj; + mergedFVMap[odfv.spec?.name!] = obj; mergedFVList.push(obj); }); objects.streamFeatureViews?.forEach((sfv) => { const obj: genericFVType = { - name: sfv.spec.name, + name: sfv.spec?.name!, type: FEAST_FV_TYPES.stream, - features: sfv.spec.features, + features: sfv.spec?.features!, object: sfv, }; - mergedFVMap[sfv.spec.name] = obj; + mergedFVMap[sfv.spec?.name!] = obj; mergedFVList.push(obj); }); diff --git a/ui/src/parsers/parseEntityRelationships.ts b/ui/src/parsers/parseEntityRelationships.ts index 8424bb7a44f..7a791fd0a15 100644 --- a/ui/src/parsers/parseEntityRelationships.ts +++ b/ui/src/parsers/parseEntityRelationships.ts @@ -1,5 +1,5 @@ -import { FeastRegistryType } from "./feastRegistry"; import { FEAST_FCO_TYPES } from "./types"; +import { feast } from "../protos"; interface EntityReference { type: FEAST_FCO_TYPES; @@ -11,26 +11,26 @@ interface EntityRelation { target: EntityReference; } -const parseEntityRelationships = (objects: FeastRegistryType) => { +const parseEntityRelationships = (objects: feast.core.Registry) => { const links: EntityRelation[] = []; objects.featureServices?.forEach((fs) => { - fs.spec.features.forEach((feature) => { + fs.spec?.features!.forEach((feature) => { links.push({ source: { type: FEAST_FCO_TYPES["featureView"], - name: feature.featureViewName, + name: feature?.featureViewName!, }, target: { type: FEAST_FCO_TYPES["featureService"], - name: fs.spec.name, + name: fs.spec?.name!, }, }); }); }); objects.featureViews?.forEach((fv) => { - fv.spec.entities.forEach((ent) => { + fv.spec?.entities?.forEach((ent) => { links.push({ source: { type: FEAST_FCO_TYPES["entity"], @@ -38,11 +38,11 @@ const parseEntityRelationships = (objects: FeastRegistryType) => { }, target: { type: FEAST_FCO_TYPES["featureView"], - name: fv.spec.name, + name: fv.spec?.name!, }, }); }); - if (fv.spec.batchSource) { + if (fv.spec?.batchSource) { links.push({ source: { type: FEAST_FCO_TYPES["dataSource"], @@ -50,54 +50,54 @@ const parseEntityRelationships = (objects: FeastRegistryType) => { }, target: { type: FEAST_FCO_TYPES["featureView"], - name: fv.spec.name, + name: fv.spec?.name!, } }) } }); objects.onDemandFeatureViews?.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.requestDataSource.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, - }, - }); - } - }); - }); + Object.values(fv.spec?.sources!).forEach((input: { [key: string]: any }) => { + if (input.requestDataSource) { + links.push({ + source: { + type: FEAST_FCO_TYPES["dataSource"], + name: input.requestDataSource.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) => { // stream source links.push({ source: { type: FEAST_FCO_TYPES["dataSource"], - name: fv.spec.streamSource.name, + name: fv.spec?.streamSource?.name!, }, target: { type: FEAST_FCO_TYPES["featureView"], - name: fv.spec.name, + name: fv.spec?.name!, }, }); @@ -105,11 +105,11 @@ const parseEntityRelationships = (objects: FeastRegistryType) => { links.push({ source: { type: FEAST_FCO_TYPES["dataSource"], - name: fv.spec.batchSource.name, + name: fv.spec?.batchSource?.name!, }, target: { type: FEAST_FCO_TYPES["featureView"], - name: fv.spec.name, + name: fv.spec?.name!, }, }); }); diff --git a/ui/src/parsers/parseIndirectRelationships.ts b/ui/src/parsers/parseIndirectRelationships.ts index d7d532ad3e7..092176c9b62 100644 --- a/ui/src/parsers/parseIndirectRelationships.ts +++ b/ui/src/parsers/parseIndirectRelationships.ts @@ -1,16 +1,16 @@ -import { FeastRegistryType } from "./feastRegistry"; import { EntityRelation } from "./parseEntityRelationships"; import { FEAST_FCO_TYPES } from "./types"; +import { feast } from "../protos"; const parseIndirectRelationships = ( relationships: EntityRelation[], - objects: FeastRegistryType + objects: feast.core.Registry ) => { const indirectLinks: EntityRelation[] = []; // Only contains Entity -> FS or DS -> FS relationships objects.featureServices?.forEach((featureService) => { - featureService.spec.features.forEach((featureView) => { + featureService.spec?.features?.forEach((featureView) => { relationships .filter( (relationship) => @@ -21,7 +21,7 @@ const parseIndirectRelationships = ( source: relationship.source, target: { type: FEAST_FCO_TYPES["featureService"], - name: featureService.spec.name, + name: featureService.spec?.name!, }, }); }); diff --git a/ui/src/parsers/types.ts b/ui/src/parsers/types.ts index 2f88eea4f06..1e515f23f34 100644 --- a/ui/src/parsers/types.ts +++ b/ui/src/parsers/types.ts @@ -5,25 +5,4 @@ enum FEAST_FCO_TYPES { featureService = "featureService", } -enum FEAST_FEATURE_VALUE_TYPES { - FLOAT = "FLOAT", - INT64 = "INT64", - STRING = "STRING", - BOOL = "BOOL", - BYTES = "BYTES", - INT32 = "INT32", - DOUBLE = "DOUBLE", - UNIX_TIMESTAMP = "UNIX_TIMESTAMP", - INVALID = "INVALID", - BYTES_LIST = "BYTES_LIST", - STRING_LIST = "STRING_LIST", - INT32_LIST = "INT32_LIST", - INT64_LIST = "INT64_LIST", - DOUBLE_LIST = "DOUBLE_LIST", - FLOAT_LIST = "FLOAT_LIST", - BOOL_LIST = "BOOL_LIST", - UNIX_TIMESTAMP_LIST = "UNIX_TIMESTAMP_LIST", - NULL = "NULL" -} - -export { FEAST_FCO_TYPES, FEAST_FEATURE_VALUE_TYPES }; +export { FEAST_FCO_TYPES }; diff --git a/ui/src/queries/useLoadFeatureViewSummaryStatistics.ts b/ui/src/queries/useLoadFeatureViewSummaryStatistics.ts deleted file mode 100644 index fea0bd9d816..00000000000 --- a/ui/src/queries/useLoadFeatureViewSummaryStatistics.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useQuery } from "react-query"; -import { useParams } from "react-router-dom"; -import { - featureViewSummaryStatisticsSchema, - FeatureViewSummaryStatisticsType, -} from "../parsers/featureViewSummaryStatistics"; - -const useLoadFeatureViewSummaryStatistics = (featureViewName: string) => { - const { projectName } = useParams(); - - const queryKey = `featureViewSummaryStatistics:${featureViewName}`; - const url = `/data/${projectName}/featureView/${featureViewName}.json`; - - return useQuery( - queryKey, - () => { - return fetch(url, { - headers: { - "Content-Type": "application/json", - }, - }) - .then((res) => { - return res.json(); - }) - .then((json) => { - const summary = featureViewSummaryStatisticsSchema.parse(json); - - return summary; - }); - }, - { - staleTime: 15 * 60 * 1000, // Given that we are reading from a registry dump, this seems reasonable for now. - } - ); -}; - -export default useLoadFeatureViewSummaryStatistics; diff --git a/ui/src/queries/useLoadRegistry.ts b/ui/src/queries/useLoadRegistry.ts index ffb06756437..be8ab65a8cd 100644 --- a/ui/src/queries/useLoadRegistry.ts +++ b/ui/src/queries/useLoadRegistry.ts @@ -1,18 +1,15 @@ import { useQuery } from "react-query"; -import { - FeastRegistrySchema, - FeastRegistryType, -} from "../parsers/feastRegistry"; import mergedFVTypes, { genericFVType } from "../parsers/mergedFVTypes"; import parseEntityRelationships, { EntityRelation, } from "../parsers/parseEntityRelationships"; import parseIndirectRelationships from "../parsers/parseIndirectRelationships"; +import { feast } from "../protos"; interface FeatureStoreAllData { project: string; description?: string; - objects: FeastRegistryType; + objects: feast.core.Registry; relationships: EntityRelation[]; mergedFVMap: Record; mergedFVList: genericFVType[]; @@ -29,10 +26,12 @@ const useLoadRegistry = (url: string) => { }, }) .then((res) => { - return res.json(); + return res.arrayBuffer(); }) - .then((json) => { - const objects = FeastRegistrySchema.parse(json); + .then((arrayBuffer) => { + + const objects = feast.core.Registry.decode(new Uint8Array(arrayBuffer)); + // const objects = FeastRegistrySchema.parse(json); const { mergedFVMap, mergedFVList } = mergedFVTypes(objects); @@ -53,7 +52,7 @@ const useLoadRegistry = (url: string) => { // }); return { - project: objects.project, + project: objects.projectMetadata[0].project!, objects, mergedFVMap, mergedFVList, diff --git a/ui/src/utils/timestamp.ts b/ui/src/utils/timestamp.ts new file mode 100644 index 00000000000..869d24870f0 --- /dev/null +++ b/ui/src/utils/timestamp.ts @@ -0,0 +1,13 @@ +import long from 'long'; +import { google } from '../protos'; + +export function toDate(ts: google.protobuf.ITimestamp) { + var seconds: number; + if (ts.seconds instanceof long) { + seconds = ts.seconds.low + } else { + seconds = ts.seconds!; + } + + return new Date(seconds * 1000); +} \ No newline at end of file diff --git a/ui/yarn.lock b/ui/yarn.lock index ad31cbeac51..948eb78796e 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -334,6 +334,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.3.tgz#b07702b982990bf6fdc1da5049a23fece4c5c3d0" integrity sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA== +"@babel/parser@^7.9.4": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.0.tgz#497fcafb1d5b61376959c1c338745ef0577aa02c" + integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz#4eda6d6c2a0aa79c70fa7b6da67763dfe2141050" @@ -1592,6 +1597,59 @@ schema-utils "^3.0.0" source-map "^0.7.3" +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + "@rollup/plugin-babel@^5.2.0": version "5.3.0" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz#9cb1c5146ddd6a4968ad96f209c50c62f92f9879" @@ -2316,11 +2374,24 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/linkify-it@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9" + integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA== + "@types/lodash@^4.14.160": version "4.14.178" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.178.tgz#341f6d2247db528d4a13ddbb374bcdc80406f4f8" integrity sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw== +"@types/markdown-it@^12.2.3": + version "12.2.3" + resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-12.2.3.tgz#0d6f6e5e413f8daaa26522904597be3d6cd93b51" + integrity sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ== + dependencies: + "@types/linkify-it" "*" + "@types/mdurl" "*" + "@types/mdast@^3.0.0": version "3.0.10" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" @@ -2328,6 +2399,11 @@ dependencies: "@types/unist" "*" +"@types/mdurl@*": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9" + integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA== + "@types/mime@^1": version "1.3.2" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" @@ -2343,6 +2419,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.13.tgz#5ed7ed7c662948335fcad6c412bb42d99ea754e3" integrity sha512-Y86MAxASe25hNzlDbsviXl8jQHb0RDvKt4c40ZJQ1Don0AAL0STLZSs4N+6gLEO55pedy7r2cLwS+ZDxPm/2Bw== +"@types/node@>=13.7.0": + version "18.7.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.18.tgz#633184f55c322e4fb08612307c274ee6d5ed3154" + integrity sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg== + "@types/node@^16.7.13": version "16.11.21" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.21.tgz#474d7589a30afcf5291f59bd49cca9ad171ffde4" @@ -2809,7 +2890,7 @@ acorn-import-assertions@^1.7.6: resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== -acorn-jsx@^5.3.1: +acorn-jsx@^5.3.1, acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== @@ -2838,6 +2919,11 @@ acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== +acorn@^8.8.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" + integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== + address@^1.0.1, address@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" @@ -3296,7 +3382,7 @@ bl@^4.1.0: inherits "^2.0.4" readable-stream "^3.4.0" -bluebird@^3.5.5: +bluebird@^3.5.5, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -3473,15 +3559,22 @@ 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" resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4" integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw== +catharsis@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.9.0.tgz#40382a168be0e6da308c277d3a2b3eb40c7d2121" + integrity sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A== + dependencies: + lodash "^4.17.15" + ccount@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" @@ -4736,6 +4829,11 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +entities@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" + integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -4815,6 +4913,18 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escodegen@^1.13.0: + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + escodegen@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" @@ -4980,6 +5090,11 @@ eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.1.0, eslint-visitor-keys@^3.2 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1" integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ== +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + eslint-webpack-plugin@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/eslint-webpack-plugin/-/eslint-webpack-plugin-3.1.1.tgz#83dad2395e5f572d6f4d919eedaa9cf902890fcb" @@ -5032,6 +5147,15 @@ eslint@^8.3.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" +espree@^9.0.0: + version "9.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.0.tgz#cd4bc3d6e9336c433265fc0aa016fc1aaf182f8a" + integrity sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw== + dependencies: + acorn "^8.8.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.3.0" + espree@^9.2.0, espree@^9.3.0: version "9.3.0" resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.0.tgz#c1240d79183b72aaee6ccfa5a90bc9111df085a8" @@ -5060,7 +5184,7 @@ esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -estraverse@^4.1.1: +estraverse@^4.1.1, estraverse@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== @@ -5536,6 +5660,17 @@ glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^8.0.0: + version "8.0.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" + integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + global-modules@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" @@ -5595,6 +5730,11 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== +graceful-fs@^4.1.9: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + graphql@^15.5.1: version "15.8.0" resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.8.0.tgz#33410e96b012fa3bdb1091cc99a94769db212b38" @@ -6862,6 +7002,34 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +js2xmlparser@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/js2xmlparser/-/js2xmlparser-4.0.2.tgz#2a1fdf01e90585ef2ae872a01bc169c6a8d5e60a" + integrity sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA== + dependencies: + xmlcreate "^2.0.4" + +jsdoc@^3.6.3: + version "3.6.11" + resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.6.11.tgz#8bbb5747e6f579f141a5238cbad4e95e004458ce" + integrity sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg== + dependencies: + "@babel/parser" "^7.9.4" + "@types/markdown-it" "^12.2.3" + bluebird "^3.7.2" + catharsis "^0.9.0" + escape-string-regexp "^2.0.0" + js2xmlparser "^4.0.2" + klaw "^3.0.0" + markdown-it "^12.3.2" + markdown-it-anchor "^8.4.1" + marked "^4.0.10" + mkdirp "^1.0.4" + requizzle "^0.2.3" + strip-json-comments "^3.1.0" + taffydb "2.6.2" + underscore "~1.13.2" + jsdom@^16.6.0: version "16.7.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" @@ -6983,6 +7151,13 @@ kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +klaw@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/klaw/-/klaw-3.0.0.tgz#b11bec9cf2492f06756d6e809ab73a2910259146" + integrity sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g== + dependencies: + graceful-fs "^4.1.9" + kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -7036,6 +7211,13 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +linkify-it@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" + integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ== + dependencies: + uc.micro "^1.0.1" + loader-runner@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" @@ -7132,6 +7314,11 @@ log-symbols@^4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" +long@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.0.tgz#2696dadf4b4da2ce3f6f6b89186085d94d52fd61" + integrity sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w== + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -7184,6 +7371,27 @@ markdown-escapes@^1.0.0: resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== +markdown-it-anchor@^8.4.1: + version "8.6.5" + resolved "https://registry.yarnpkg.com/markdown-it-anchor/-/markdown-it-anchor-8.6.5.tgz#30c4bc5bbff327f15ce3c429010ec7ba75e7b5f8" + integrity sha512-PI1qEHHkTNWT+X6Ip9w+paonfIQ+QZP9sCeMYi47oqhH+EsW8CrJ8J7CzV19QVOj6il8ATGbK2nTECj22ZHGvQ== + +markdown-it@^12.3.2: + version "12.3.2" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90" + integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg== + dependencies: + argparse "^2.0.1" + entities "~2.1.0" + linkify-it "^3.0.1" + mdurl "^1.0.1" + uc.micro "^1.0.5" + +marked@^4.0.10: + version "4.1.0" + resolved "https://registry.yarnpkg.com/marked/-/marked-4.1.0.tgz#3fc6e7485f21c1ca5d6ec4a39de820e146954796" + integrity sha512-+Z6KDjSPa6/723PQYyc1axYZpYYpDnECDaU6hkaf5gqBieBkMKYReL5hteF2QizhlMbgbo8umXl/clZ67+GlsA== + match-sorter@^6.0.2: version "6.3.1" resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.1.tgz#98cc37fda756093424ddf3cbc62bfe9c75b92bda" @@ -7228,7 +7436,7 @@ mdn-data@~1.1.0: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.4.tgz#50b5d4ffc4575276573c4eedb8780812a8419f01" integrity sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA== -mdurl@^1.0.0: +mdurl@^1.0.0, mdurl@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= @@ -7355,6 +7563,11 @@ mkdirp@^0.5.5, mkdirp@~0.5.1: dependencies: minimist "^1.2.5" +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + moment@^2.29.1: version "2.29.4" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" @@ -8502,6 +8715,40 @@ property-information@^5.0.0, property-information@^5.3.0: dependencies: xtend "^4.0.0" +protobufjs-cli@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/protobufjs-cli/-/protobufjs-cli-1.0.2.tgz#905fc49007cf4aaf3c45d5f250eb294eedeea062" + integrity sha512-cz9Pq9p/Zs7okc6avH20W7QuyjTclwJPgqXG11jNaulfS3nbVisID8rC+prfgq0gbZE0w9LBFd1OKFF03kgFzg== + dependencies: + chalk "^4.0.0" + escodegen "^1.13.0" + espree "^9.0.0" + estraverse "^5.1.0" + glob "^8.0.0" + jsdoc "^3.6.3" + minimist "^1.2.0" + semver "^7.1.2" + tmp "^0.2.1" + uglify-js "^3.7.7" + +protobufjs@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.1.1.tgz#0117befb4b0f5a49d028e93f2ca62c3c1f5e7c65" + integrity sha512-d0nMQqS/aT3lfV8bKi9Gbg73vPd2LcDdTDOu6RE/M+h9DY8g1EmDzk3ADPccthEWfTBjkR2oxNdx9Gs8YubT+g== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -9104,6 +9351,13 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= +requizzle@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/requizzle/-/requizzle-0.2.3.tgz#4675c90aacafb2c036bd39ba2daa4a1cb777fded" + integrity sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ== + dependencies: + lodash "^4.17.14" + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -9379,6 +9633,13 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.1.2: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + semver@^7.3.2, semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" @@ -9929,6 +10190,11 @@ tabbable@^5.2.1: resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-5.3.2.tgz#66d6119ee8a533634c3f17deb0caa1c379e36ac7" integrity sha512-6G/8EWRFx8CiSe2++/xHhXkmCRq2rHtDtZbQFHx34cvDfZzIBfvwG9zGUNTWMXWLCYvDj3aQqOzdl3oCxKuBkQ== +taffydb@2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268" + integrity sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA== + tailwindcss@^3.0.2: version "3.0.18" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.18.tgz#ea4825e6496d77dc21877b6b61c7cc56cda3add5" @@ -10060,6 +10326,13 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -10221,6 +10494,16 @@ typescript@^4.4.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== +uc.micro@^1.0.1, uc.micro@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + +uglify-js@^3.7.7: + version "3.17.0" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.0.tgz#55bd6e9d19ce5eef0d5ad17cd1f587d85b180a85" + integrity sha512-aTeNPVmgIMPpm1cxXr2Q/nEbvkmV8yq66F3om7X3P/cvOXQ0TMQ64Wk63iyT1gPlmdmGzjGpyLh1f3y8MZWXGg== + unbox-primitive@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" @@ -10231,6 +10514,11 @@ unbox-primitive@^1.0.1: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" +underscore@~1.13.2: + version "1.13.4" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.4.tgz#7886b46bbdf07f768e0052f1828e1dcab40c0dee" + integrity sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ== + unherit@^1.0.4: version "1.1.3" resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" @@ -10988,6 +11276,11 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xmlcreate@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/xmlcreate/-/xmlcreate-2.0.4.tgz#0c5ab0f99cdd02a81065fa9cd8f8ae87624889be" + integrity sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg== + xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" @@ -11050,9 +11343,9 @@ yocto-queue@^0.1.0: integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== zod@^3.11.6: - version "3.11.6" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.11.6.tgz#e43a5e0c213ae2e02aefe7cb2b1a6fa3d7f1f483" - integrity sha512-daZ80A81I3/9lIydI44motWe6n59kRBfNzTuS2bfzVh1nAXi667TOTWWtatxyG+fwgNUiagSj/CWZwRRbevJIg== + version "3.19.1" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.19.1.tgz#112f074a97b50bfc4772d4ad1576814bd8ac4473" + integrity sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA== zwitch@^1.0.0: version "1.0.5"