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/ui/README.md b/sdk/python/feast/ui/README.md index 0c11dcf134c..220f40a2251 100644 --- a/sdk/python/feast/ui/README.md +++ b/sdk/python/feast/ui/README.md @@ -22,4 +22,7 @@ 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, do `yarn link` in ui/ and then come here to do `yarn link @feast-dev/feast` \ No newline at end of file diff --git a/sdk/python/feast/ui/package.json b/sdk/python/feast/ui/package.json index 358aa2cdd24..6ffebcc834d 100644 --- a/sdk/python/feast/ui/package.json +++ b/sdk/python/feast/ui/package.json @@ -4,7 +4,7 @@ "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", "@testing-library/jest-dom": "^5.16.4", @@ -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..1b9cf5f067f 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" @@ -8403,13 +8358,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 +8437,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 +8482,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 +8497,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 +8574,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 +8958,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 73165523252..7d50b3e086b 100644 --- a/ui/package.json +++ b/ui/package.json @@ -25,8 +25,7 @@ "react-router-dom": "6", "react-scripts": "^5.0.0", "typescript": "^4.4.2", - "use-query-params": "^1.2.3", - "zod": "^3.11.6" + "use-query-params": "^1.2.3" }, "dependencies": { "@elastic/datemath": "^5.0.3", @@ -41,20 +40,21 @@ "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", "react-scripts": "^5.0.0", - "use-query-params": "^1.2.3", - "zod": "^3.11.6" + "use-query-params": "^1.2.3" }, "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 +92,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/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/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 11a2a0ab6a5..92adfe1501d 100644 --- a/ui/src/pages/features/FeatureOverviewTab.tsx +++ b/ui/src/pages/features/FeatureOverviewTab.tsx @@ -16,6 +16,7 @@ import TagsDisplay from "../../components/TagsDisplay"; 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(); @@ -51,7 +52,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 f65a0872220..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)).optional(), - lastUpdatedTimestamp: z.string().transform((val) => new Date(val)).optional(), - }), -}); - -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/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..e056cad6179 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== @@ -3482,6 +3568,13 @@ case-sensitive-paths-webpack-plugin@^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" @@ -11049,10 +11342,10 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -zod@^3.11.6: - version "3.11.6" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.11.6.tgz#e43a5e0c213ae2e02aefe7cb2b1a6fa3d7f1f483" - integrity sha512-daZ80A81I3/9lIydI44motWe6n59kRBfNzTuS2bfzVh1nAXi667TOTWWtatxyG+fwgNUiagSj/CWZwRRbevJIg== +zod@^3.19.1: + 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"