diff --git a/README.md b/README.md index ef8f66a11..37ae95883 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ Model registry provides a central repository for model developers to store and m - [playground](https://petstore.swagger.io/?url=https://raw.githubusercontent.com/kubeflow/model-registry/main/api/openapi/model-registry.yaml) - [license scanning](https://github.com/kubeflow/model-registry/issues/323) - [monitoring image quality](https://github.com/kubeflow/model-registry/issues/327) +8. [UI](.clients/ui/README.md) ## Pre-requisites: - go >= 1.21 diff --git a/clients/python/Makefile b/clients/python/Makefile index ab6c1698c..89854bbfe 100644 --- a/clients/python/Makefile +++ b/clients/python/Makefile @@ -21,11 +21,11 @@ deploy-latest-mr: .PHONY: test-e2e test-e2e: deploy-latest-mr - poetry run pytest --e2e -s + poetry run pytest --e2e -s -rA .PHONY: test test: - poetry run pytest -s + poetry run pytest -s -rA .PHONY: lint lint: diff --git a/clients/python/noxfile.py b/clients/python/noxfile.py index ae44d7022..3824797b1 100644 --- a/clients/python/noxfile.py +++ b/clients/python/noxfile.py @@ -61,6 +61,7 @@ def tests(session: Session) -> None: ) session.run( "pytest", + "-rA", *session.posargs, ) @@ -81,6 +82,7 @@ def e2e_tests(session: Session) -> None: session.run( "pytest", "--e2e", + "-rA", "--cov", "--cov-config=pyproject.toml", *session.posargs, diff --git a/clients/python/poetry.lock b/clients/python/poetry.lock index 72e30d5f1..64b2dab92 100644 --- a/clients/python/poetry.lock +++ b/clients/python/poetry.lock @@ -125,13 +125,13 @@ speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] [[package]] name = "aiohttp-retry" -version = "2.9.0" +version = "2.9.1" description = "Simple retry client for aiohttp" optional = false python-versions = ">=3.7" files = [ - {file = "aiohttp_retry-2.9.0-py3-none-any.whl", hash = "sha256:7661af92471e9a96c69d9b8f32021360272073397e6a15bc44c1726b12f46056"}, - {file = "aiohttp_retry-2.9.0.tar.gz", hash = "sha256:92c47f1580040208bac95d9a8389a87227ef22758530f2e3f4683395e42c41b5"}, + {file = "aiohttp_retry-2.9.1-py3-none-any.whl", hash = "sha256:66d2759d1921838256a05a3f80ad7e724936f083e35be5abb5e16eed6be6dc54"}, + {file = "aiohttp_retry-2.9.1.tar.gz", hash = "sha256:8eb75e904ed4ee5c2ec242fefe85bf04240f685391c4879d8f541d6028ff01f1"}, ] [package.dependencies] @@ -722,13 +722,13 @@ files = [ [[package]] name = "huggingface-hub" -version = "0.26.1" +version = "0.26.2" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = true python-versions = ">=3.8.0" files = [ - {file = "huggingface_hub-0.26.1-py3-none-any.whl", hash = "sha256:5927a8fc64ae68859cd954b7cc29d1c8390a5e15caba6d3d349c973be8fdacf3"}, - {file = "huggingface_hub-0.26.1.tar.gz", hash = "sha256:414c0d9b769eecc86c70f9d939d0f48bb28e8461dd1130021542eff0212db890"}, + {file = "huggingface_hub-0.26.2-py3-none-any.whl", hash = "sha256:98c2a5a8e786c7b2cb6fdeb2740893cba4d53e312572ed3d8afafda65b128c46"}, + {file = "huggingface_hub-0.26.2.tar.gz", hash = "sha256:b100d853465d965733964d123939ba287da60a547087783ddff8a323f340332b"}, ] [package.dependencies] @@ -1401,17 +1401,17 @@ testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] name = "pytest-cov" -version = "5.0.0" +version = "6.0.0" description = "Pytest plugin for measuring coverage." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, - {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, + {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, + {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, ] [package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} +coverage = {version = ">=7.5", extras = ["toml"]} pytest = ">=4.6" [package.extras] @@ -1514,29 +1514,29 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.7.1" +version = "0.7.3" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.7.1-py3-none-linux_armv6l.whl", hash = "sha256:cb1bc5ed9403daa7da05475d615739cc0212e861b7306f314379d958592aaa89"}, - {file = "ruff-0.7.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:27c1c52a8d199a257ff1e5582d078eab7145129aa02721815ca8fa4f9612dc35"}, - {file = "ruff-0.7.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:588a34e1ef2ea55b4ddfec26bbe76bc866e92523d8c6cdec5e8aceefeff02d99"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94fc32f9cdf72dc75c451e5f072758b118ab8100727168a3df58502b43a599ca"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:985818742b833bffa543a84d1cc11b5e6871de1b4e0ac3060a59a2bae3969250"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32f1e8a192e261366c702c5fb2ece9f68d26625f198a25c408861c16dc2dea9c"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:699085bf05819588551b11751eff33e9ca58b1b86a6843e1b082a7de40da1565"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:344cc2b0814047dc8c3a8ff2cd1f3d808bb23c6658db830d25147339d9bf9ea7"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4316bbf69d5a859cc937890c7ac7a6551252b6a01b1d2c97e8fc96e45a7c8b4a"}, - {file = "ruff-0.7.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79d3af9dca4c56043e738a4d6dd1e9444b6d6c10598ac52d146e331eb155a8ad"}, - {file = "ruff-0.7.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c5c121b46abde94a505175524e51891f829414e093cd8326d6e741ecfc0a9112"}, - {file = "ruff-0.7.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8422104078324ea250886954e48f1373a8fe7de59283d747c3a7eca050b4e378"}, - {file = "ruff-0.7.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:56aad830af8a9db644e80098fe4984a948e2b6fc2e73891538f43bbe478461b8"}, - {file = "ruff-0.7.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:658304f02f68d3a83c998ad8bf91f9b4f53e93e5412b8f2388359d55869727fd"}, - {file = "ruff-0.7.1-py3-none-win32.whl", hash = "sha256:b517a2011333eb7ce2d402652ecaa0ac1a30c114fbbd55c6b8ee466a7f600ee9"}, - {file = "ruff-0.7.1-py3-none-win_amd64.whl", hash = "sha256:f38c41fcde1728736b4eb2b18850f6d1e3eedd9678c914dede554a70d5241307"}, - {file = "ruff-0.7.1-py3-none-win_arm64.whl", hash = "sha256:19aa200ec824c0f36d0c9114c8ec0087082021732979a359d6f3c390a6ff2a37"}, - {file = "ruff-0.7.1.tar.gz", hash = "sha256:9d8a41d4aa2dad1575adb98a82870cf5db5f76b2938cf2206c22c940034a36f4"}, + {file = "ruff-0.7.3-py3-none-linux_armv6l.whl", hash = "sha256:34f2339dc22687ec7e7002792d1f50712bf84a13d5152e75712ac08be565d344"}, + {file = "ruff-0.7.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fb397332a1879b9764a3455a0bb1087bda876c2db8aca3a3cbb67b3dbce8cda0"}, + {file = "ruff-0.7.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:37d0b619546103274e7f62643d14e1adcbccb242efda4e4bdb9544d7764782e9"}, + {file = "ruff-0.7.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59f0c3ee4d1a6787614e7135b72e21024875266101142a09a61439cb6e38a5"}, + {file = "ruff-0.7.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:44eb93c2499a169d49fafd07bc62ac89b1bc800b197e50ff4633aed212569299"}, + {file = "ruff-0.7.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d0242ce53f3a576c35ee32d907475a8d569944c0407f91d207c8af5be5dae4e"}, + {file = "ruff-0.7.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6b6224af8b5e09772c2ecb8dc9f3f344c1aa48201c7f07e7315367f6dd90ac29"}, + {file = "ruff-0.7.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c50f95a82b94421c964fae4c27c0242890a20fe67d203d127e84fbb8013855f5"}, + {file = "ruff-0.7.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f3eff9961b5d2644bcf1616c606e93baa2d6b349e8aa8b035f654df252c8c67"}, + {file = "ruff-0.7.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8963cab06d130c4df2fd52c84e9f10d297826d2e8169ae0c798b6221be1d1d2"}, + {file = "ruff-0.7.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:61b46049d6edc0e4317fb14b33bd693245281a3007288b68a3f5b74a22a0746d"}, + {file = "ruff-0.7.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:10ebce7696afe4644e8c1a23b3cf8c0f2193a310c18387c06e583ae9ef284de2"}, + {file = "ruff-0.7.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3f36d56326b3aef8eeee150b700e519880d1aab92f471eefdef656fd57492aa2"}, + {file = "ruff-0.7.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5d024301109a0007b78d57ab0ba190087b43dce852e552734ebf0b0b85e4fb16"}, + {file = "ruff-0.7.3-py3-none-win32.whl", hash = "sha256:4ba81a5f0c5478aa61674c5a2194de8b02652f17addf8dfc40c8937e6e7d79fc"}, + {file = "ruff-0.7.3-py3-none-win_amd64.whl", hash = "sha256:588a9ff2fecf01025ed065fe28809cd5a53b43505f48b69a1ac7707b1b7e4088"}, + {file = "ruff-0.7.3-py3-none-win_arm64.whl", hash = "sha256:1713e2c5545863cdbfe2cbce21f69ffaf37b813bfd1fb3b90dc9a6f1963f5a8c"}, + {file = "ruff-0.7.3.tar.gz", hash = "sha256:e1d1ba2e40b6e71a61b063354d04be669ab0d39c352461f3d789cac68b54a313"}, ] [[package]] @@ -2192,4 +2192,4 @@ hf = ["huggingface-hub"] [metadata] lock-version = "2.0" python-versions = ">= 3.9, < 4.0" -content-hash = "fbda101f15a725c406f600ad8756eaf3fa3188c822b7342f1aaf1603fd5834b8" +content-hash = "31ca41d8f95f5d4d83894bdb45ae2802ce23448799ae249d038c3ef47e167f10" diff --git a/clients/python/pyproject.toml b/clients/python/pyproject.toml index 631155004..1fe681777 100644 --- a/clients/python/pyproject.toml +++ b/clients/python/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "model-registry" -version = "0.2.10" +version = "0.2.11" description = "Client for Kubeflow Model Registry" authors = ["Isabella Basso do Amaral "] license = "Apache-2.0" @@ -44,7 +44,7 @@ sphinx-autobuild = ">=2021.3.14,<2025.0.0" [tool.poetry.group.dev.dependencies] pytest = ">=7.4.2,<9.0.0" coverage = { extras = ["toml"], version = "^7.3.2" } -pytest-cov = ">=4.1,<6.0" +pytest-cov = ">=4.1,<7.0" ruff = ">=0.5.2,<0.8.0" mypy = "^1.7.0" pytest-asyncio = ">=0.23.7,<0.25.0" diff --git a/clients/python/src/model_registry/__init__.py b/clients/python/src/model_registry/__init__.py index ba6df5fcd..20ca54539 100644 --- a/clients/python/src/model_registry/__init__.py +++ b/clients/python/src/model_registry/__init__.py @@ -1,6 +1,6 @@ """Main package for the Kubeflow model registry.""" -__version__ = "0.2.10" +__version__ = "0.2.11" from ._client import ModelRegistry diff --git a/clients/python/tests/test_client.py b/clients/python/tests/test_client.py index ea3257dd4..0089f8dbb 100644 --- a/clients/python/tests/test_client.py +++ b/clients/python/tests/test_client.py @@ -230,6 +230,39 @@ async def test_update_preserves_model_info(client: ModelRegistry): assert updated_ma.model_format_version == model_fmt_version +@pytest.mark.e2e +async def test_update_existing_model_artifact(client: ModelRegistry): + """Updating uri (or other properties) by re-using and call to update + + reported via slack + """ + name = "test_model" + version = "1.0.0" + rm = client.register_model( + name, + "s3", + model_format_name="test_format", + model_format_version="test_version", + version=version, + ) + assert rm.id + mv = client.get_model_version(name, version) + assert mv + assert mv.id + ma = client.get_model_artifact(name, version) + assert ma + assert ma.id + + something_else = "https://something.else/model.onnx" + ma.uri = something_else + response = client.update(ma) + assert response + assert response.uri == something_else + + ma = client.get_model_artifact(name, version) + assert ma.uri == something_else + + @pytest.mark.e2e async def test_get(client: ModelRegistry): name = "test_model" diff --git a/clients/ui/.env b/clients/ui/.env new file mode 100644 index 000000000..02d80d092 --- /dev/null +++ b/clients/ui/.env @@ -0,0 +1,5 @@ + +############### Default settings ############### +CONTAINER_TOOL=docker +IMG_BFF=kubeflow/model-registry-bff:dev-latest +IMG_FRONTEND=kubeflow/model-registry-ui:dev-latest diff --git a/clients/ui/.gitignore b/clients/ui/.gitignore new file mode 100644 index 000000000..93251d2f4 --- /dev/null +++ b/clients/ui/.gitignore @@ -0,0 +1,8 @@ +# editor +.idea/ +.vscode/ + +# misc +.DS_Store + +.env*.local diff --git a/clients/ui/Makefile b/clients/ui/Makefile new file mode 100644 index 000000000..648d578cf --- /dev/null +++ b/clients/ui/Makefile @@ -0,0 +1,79 @@ +DEFAULT_ENV_FILE := .env +ifneq ("$(wildcard $(DEFAULT_ENV_FILE))","") +include ${DEFAULT_ENV_FILE} +export $(shell sed 's/=.*//' ${DEFAULT_ENV_FILE}) +endif + +DEV_ENV_FILE := .env.development +ifneq ("$(wildcard $(DEV_ENV_FILE))","") +include ${DEV_ENV_FILE} +export $(shell sed 's/=.*//' ${DEV_ENV_FILE}) +endif + +ENV_FILE := .env.local +ifneq ("$(wildcard $(ENV_FILE))","") +include ${ENV_FILE} +export $(shell sed 's/=.*//' ${ENV_FILE}) +endif + +.PHONY: all +all: build + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + + +############ Dev Environment ############ + +.PHONY: dev-install-dependencies +dev-install-dependencies: + cd frontend && npm install + +.PHONY: dev-bff +dev-bff: + cd bff && make run PORT=4000 MOCK_K8S_CLIENT=true MOCK_MR_CLIENT=true + +.PHONY: dev-frontend +dev-frontend: + cd frontend && npm run start:dev + +.PHONY: dev-start +dev-start: + make -j 2 dev-bff dev-frontend + +############ Build ############ + +.PHONY: build-bff +build-bff: + $(CONTAINER_TOOL) build -t ${IMG_BFF} ./bff + +.PHONY: build-frontend +build-frontend: + $(CONTAINER_TOOL) build -t ${IMG_FRONTEND} ./frontend + +.PHONY: build +build: build-bff build-frontend + +############ Push ############ + +.PHONY: push-bff +push-bff: + ${CONTAINER_TOOL} push ${IMG_BFF} + +.PHONY: push-frontend +push-frontend: + ${CONTAINER_TOOL} push ${IMG_FRONTEND} + +.PHONY: push +push: push-bff push-frontend + +############ Deployment ############ + +.PHONY: docker-compose +docker-compose: + $(CONTAINER_TOOL) compose -f docker-compose.yaml up + +.PHONY: kind-deployment +kind-deployment: + ./scripts/deploy_kind_cluster.sh diff --git a/clients/ui/README.md b/clients/ui/README.md new file mode 100644 index 000000000..287b62450 --- /dev/null +++ b/clients/ui/README.md @@ -0,0 +1,113 @@ +[frontend requirements]: ./frontend/docs/dev-setup.md#requirements +[BFF requirements]: ./bff/README.md#pre-requisites +[frontend dev setup]: ./frontend/docs/dev-setup.md#development +[BFF dev setup]: ./bff/README.md#development + +# Model Registry UI + +## Overview + +The Model Registry UI is a standalone web app for Kubeflow Model Registry. In this repository, you will find the frontend and backend for the Model Registry UI. + +## Prerequisites + +* [Frontend requirements] +* [BFF requirements] + +## Set Up + +### Development + +To run the a mocked dev environment you can either: + +* Use the makefile command to install dependencies `make dev-install-dependencies` and then start the dev environment `make dev-start`. + +* Or follow the [frontend dev setup] and [BFF dev setup]. + +### Docker deployment + +To build the Model Registry UI container, run the following command: + +```shell +make docker-compose +``` + +### Kubernetes Deployment + +For a in-depth guide on how to deploy the Model Registry UI, please refer to the [local kubernetes deployment](./bff/docs/dev-guide.md) documentation. + +To quickly enable the Model Registry UI in your Kind cluster, you can use the following command: + +```shell +make kind-deployment +``` + +## OpenAPI Specification + +You can find the OpenAPI specification for the Model Registry UI in the [openapi](./api/openapi) directory. +A live version of the OpenAPI specification can be found [here](https://editor.swagger.io/?url=https://raw.githubusercontent.com/kubeflow/model-registry/main/clients/ui/api/openapi/mod-arch.yaml). + +## Environment Variables + +The following environment variables are used to configure the deployment and development environment for the Model Registry UI. These variables should be defined in a `.env.local` file in the `clients/ui` directory of the project. **This values will affect the build and push commands**. + +### `CONTAINER_TOOL` + +* **Description**: Specifies the container tool to be used for building and running containers. +* **Default Value**: `docker` +* **Possible Values**: `docker`, `podman`, etc. +* **Example**: `CONTAINER_TOOL=docker` + +### `IMG_BFF` + +* **Description**: Specifies the image name and tag for the Backend For Frontend (BFF) service. +* **Default Value**: `model-registry-bff:latest` +* **Example**: `IMG_BFF=model-registry-bff:latest` + +### `IMG_FRONTEND` + +* **Description**: Specifies the image name and tag for the frontend service. +* **Default Value**: `model-registry-frontend:latest` +* **Example**: `IMG_FRONTEND=model-registry-frontend:latest` + +### Example `.env.local` File + +Here is an example of what your `.env.local` file might look like: + +```shell +CONTAINER_TOOL=docker +IMG_BFF=model-registry-bff:latest +IMG_FRONTEND=model-registry-frontend:latest +``` + +## Build and Push Commands + +The following Makefile targets are used to build and push the Docker images for the Backend For Frontend (BFF) and frontend services. These targets utilize the environment variables defined in the `.env.local` file. + +### Build Commands + +* **`build-bff`**: Builds the Docker image for the BFF service. + * Command: `make build-bff` + * This command uses the `CONTAINER_TOOL` and `IMG_BFF` environment variables to build the image. + +* **`build-frontend`**: Builds the Docker image for the frontend service. + * Command: `make build-frontend` + * This command uses the `CONTAINER_TOOL` and `IMG_FRONTEND` environment variables to build the image. + +* **`build`**: Builds the Docker images for both the BFF and frontend services. + * Command: `make build` + * This command runs both `build-bff` and `build-frontend` targets. + +### Push Commands + +* **`push-bff`**: Pushes the Docker image for the BFF service to the container registry. + * Command: `make push-bff` + * This command uses the `CONTAINER_TOOL` and `IMG_BFF` environment variables to push the image. + +* **`push-frontend`**: Pushes the Docker image for the frontend service to the container registry. + * Command: `make push-frontend` + * This command uses the `CONTAINER_TOOL` and `IMG_FRONTEND` environment variables to push the image. + +* **`push`**: Pushes the Docker images for both the BFF and frontend services to the container registry. + * Command: `make push` + * This command runs both `push-bff` and `push-frontend` targets. diff --git a/clients/ui/api/openapi/mod-arch.yaml b/clients/ui/api/openapi/mod-arch.yaml index 3479de762..d17494682 100644 --- a/clients/ui/api/openapi/mod-arch.yaml +++ b/clients/ui/api/openapi/mod-arch.yaml @@ -86,7 +86,13 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/ModelVersionUpdate" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/ModelVersionUpdate" required: true tags: - ModelRegistryService @@ -141,7 +147,13 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/RegisteredModelCreate" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/RegisteredModelCreate" required: true tags: - ModelRegistryService @@ -186,7 +198,13 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/RegisteredModelUpdate" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/RegisteredModelUpdate" required: true tags: - ModelRegistryService @@ -244,7 +262,13 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Artifact" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/Artifact" required: true tags: - ModelRegistryService @@ -305,7 +329,13 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/ModelVersion" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/ModelVersion" required: true tags: - ModelRegistryService @@ -972,99 +1002,195 @@ components: content: application/json: schema: - $ref: "#/components/schemas/Config" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/Config" description: A response containing a list of ModelArtifact entities. ModelRegistryRespone: content: application/json: schema: - type: array - items: - $ref: "#/components/schemas/ModelRegistry" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + type: array + items: + $ref: "#/components/schemas/ModelRegistry" description: A response containing a list of ModelArtifact entities. ModelArtifactListResponse: content: application/json: schema: - $ref: "#/components/schemas/ModelArtifactList" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/ModelArtifactList" description: A response containing a list of ModelArtifact entities. ModelArtifactResponse: content: application/json: schema: - $ref: "#/components/schemas/ModelArtifact" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/ModelArtifact" description: A response containing a `ModelArtifact` entity. ModelVersionListResponse: content: application/json: schema: - $ref: "#/components/schemas/ModelVersionList" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/ModelVersionList" description: A response containing a list of `ModelVersion` entities. ModelVersionResponse: content: application/json: schema: - $ref: "#/components/schemas/ModelVersion" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/ModelVersion" description: A response containing a `ModelVersion` entity. RegisteredModelListResponse: content: application/json: schema: - $ref: "#/components/schemas/RegisteredModelList" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/RegisteredModelList" description: A response containing a list of `RegisteredModel` entities. RegisteredModelResponse: content: application/json: schema: - $ref: "#/components/schemas/RegisteredModel" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/RegisteredModel" description: A response containing a `RegisteredModel` entity. ArtifactResponse: content: application/json: schema: - $ref: "#/components/schemas/Artifact" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/Artifact" description: A response containing an `Artifact` entity. ArtifactListResponse: content: application/json: schema: - $ref: "#/components/schemas/ArtifactList" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/ArtifactList" description: A response containing a list of `Artifact` entities. ServingEnvironmentListResponse: content: application/json: schema: - $ref: "#/components/schemas/ServingEnvironmentList" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/ServingEnvironmentList" description: A response containing a list of `ServingEnvironment` entities. ServingEnvironmentResponse: content: application/json: schema: - $ref: "#/components/schemas/ServingEnvironment" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/ServingEnvironment" description: A response containing a `ServingEnvironment` entity. InferenceServiceListResponse: content: application/json: schema: - $ref: "#/components/schemas/InferenceServiceList" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/InferenceServiceList" description: A response containing a list of `InferenceService` entities. InferenceServiceResponse: content: application/json: schema: - $ref: "#/components/schemas/InferenceService" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/InferenceService" description: A response containing a `InferenceService` entity. ServeModelListResponse: content: application/json: schema: - $ref: "#/components/schemas/ServeModelList" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/ServeModelList" description: A response containing a list of `ServeModel` entities. ServeModelResponse: content: application/json: schema: - $ref: "#/components/schemas/ServeModel" + type: object + properties: + metadata: + type: object + description: Metadata about the response + data: + $ref: "#/components/schemas/ServeModel" description: A response containing a `ServeModel` entity. parameters: modelRegistryName: diff --git a/clients/ui/bff/internal/api/middleware.go b/clients/ui/bff/internal/api/middleware.go index 04cfc7806..743cd2567 100644 --- a/clients/ui/bff/internal/api/middleware.go +++ b/clients/ui/bff/internal/api/middleware.go @@ -5,13 +5,13 @@ import ( "fmt" "github.com/julienschmidt/httprouter" "github.com/kubeflow/model-registry/ui/bff/internal/integrations" - "k8s.io/client-go/rest" "net/http" ) type contextKey string const httpClientKey contextKey = "httpClientKey" +const userAccessToken = "x-forwarded-access-token" func (app *App) RecoverPanic(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -47,7 +47,7 @@ func (app *App) AttachRESTClient(handler func(http.ResponseWriter, *http.Request return } var bearerToken string - bearerToken, err = resolveBearerToken(app.kubernetesClient) + bearerToken, err = resolveBearerToken(app.kubernetesClient, r.Header) if err != nil { app.serverErrorResponse(w, r, fmt.Errorf("failed to resolve BearerToken): %v", err)) return @@ -63,21 +63,24 @@ func (app *App) AttachRESTClient(handler func(http.ResponseWriter, *http.Request } } -func resolveBearerToken(k8s integrations.KubernetesClientInterface) (string, error) { +func resolveBearerToken(k8s integrations.KubernetesClientInterface, header http.Header) (string, error) { var bearerToken string - _, err := rest.InClusterConfig() - if err == nil { + //check if I'm inside cluster + if k8s.IsInCluster() { //in cluster - //TODO (eder) load bearerToken probably from x-forwarded-access-bearerToken - return "", fmt.Errorf("failed to create Rest client (not implemented yet - inside cluster): %v", err) + bearerToken = header.Get(userAccessToken) + if bearerToken == "" { + return "", fmt.Errorf("failed to create Rest client (not able to get bearerToken on cluster)") + } } else { //off cluster (development) + var err error bearerToken, err = k8s.BearerToken() if err != nil { return "", fmt.Errorf("failed to fetch BearerToken in development mode: %v", err) } } - return bearerToken, err + return bearerToken, nil } func resolveModelRegistryURL(id string, client integrations.KubernetesClientInterface) (string, error) { diff --git a/clients/ui/bff/internal/integrations/k8s.go b/clients/ui/bff/internal/integrations/k8s.go index 60729a100..2dd2c4c2d 100644 --- a/clients/ui/bff/internal/integrations/k8s.go +++ b/clients/ui/bff/internal/integrations/k8s.go @@ -3,6 +3,7 @@ package integrations import ( "context" "fmt" + "k8s.io/client-go/rest" "log/slog" "os" "time" @@ -22,6 +23,7 @@ type KubernetesClientInterface interface { GetServiceDetails() ([]ServiceDetails, error) BearerToken() (string, error) Shutdown(ctx context.Context, logger *slog.Logger) error + IsInCluster() bool } type ServiceDetails struct { @@ -126,6 +128,11 @@ func (kc *KubernetesClient) Shutdown(ctx context.Context, logger *slog.Logger) e } } +func (kc *KubernetesClient) IsInCluster() bool { + _, err := rest.InClusterConfig() + return err == nil +} + func (kc *KubernetesClient) BearerToken() (string, error) { return kc.Token, nil } diff --git a/clients/ui/docker-compose.yaml b/clients/ui/docker-compose.yaml index 6c5f4bd74..7fbc2c8ca 100644 --- a/clients/ui/docker-compose.yaml +++ b/clients/ui/docker-compose.yaml @@ -5,7 +5,7 @@ services: ports: - 8080:8080 environment: - API_URL: http://model-registry-bff:4001 + API_URL: http://model-registry-bff:4000 networks: - model_registry depends_on: @@ -15,6 +15,7 @@ services: container_name: model-registry-bff command: - "--mock-k8s-client=true" + - "--mock-mr-client=true" networks: - model_registry diff --git a/clients/ui/frontend/Dockerfile b/clients/ui/frontend/Dockerfile index 0e787fdee..c25a2b1c6 100644 --- a/clients/ui/frontend/Dockerfile +++ b/clients/ui/frontend/Dockerfile @@ -10,7 +10,7 @@ RUN npm run build FROM nginxinc/nginx-unprivileged -ENV API_URL="http://localhost:4001" +ENV API_URL="http://localhost:4000" ENV NGINX_ENVSUBST_FILTER="API_URL" COPY --from=build-stage /usr/src/app/dist/ "/usr/share/nginx/html" diff --git a/clients/ui/frontend/README.md b/clients/ui/frontend/README.md index c8f062d7d..63ca41ba7 100644 --- a/clients/ui/frontend/README.md +++ b/clients/ui/frontend/README.md @@ -7,7 +7,7 @@ The Kubeflow Model Registry UI is a standalone web app for Kubeflow Model Registry. -## Contributing: +## Contributing Individual bug fixes are welcome, it is recommended that you create a bug [issue] at the same time to describe the fix you're applying. If you are unsure how best to solve it, start with the issue and note your desire to contribute. diff --git a/clients/ui/frontend/docs/dev-setup.md b/clients/ui/frontend/docs/dev-setup.md index 327a40577..2fe6e9653 100644 --- a/clients/ui/frontend/docs/dev-setup.md +++ b/clients/ui/frontend/docs/dev-setup.md @@ -8,10 +8,6 @@ This project requires the following tools to be installed on your system: - Node recommended version -> `20.17.0` - NPM recommended version -> `10.8.2` -### Additional tooling - -[TBD] - ## Development 1. Clone the repository diff --git a/clients/ui/frontend/package-lock.json b/clients/ui/frontend/package-lock.json index 3cae3116d..1f020b0c5 100644 --- a/clients/ui/frontend/package-lock.json +++ b/clients/ui/frontend/package-lock.json @@ -55,7 +55,7 @@ "cypress-axe": "^1.5.0", "cypress-high-resolution": "^1.0.0", "cypress-mochawesome-reporter": "^3.8.2", - "cypress-multi-reporters": "^1.6.4", + "cypress-multi-reporters": "^2.0.4", "dotenv": "^16.4.5", "dotenv-webpack": "^8.1.0", "expect": "^29.7.0", @@ -65,11 +65,11 @@ "jest-environment-jsdom": "^29.7.0", "junit-report-merger": "^7.0.0", "mini-css-extract-plugin": "^2.9.0", - "postcss": "^8.4.47", + "postcss": "^8.4.48", "prettier": "^3.3.3", "prop-types": "^15.8.1", "raw-loader": "^4.0.2", - "react-router-dom": "^6.26.1", + "react-router-dom": "^6.28.0", "regenerator-runtime": "^0.14.1", "rimraf": "^6.0.1", "sass-loader": "^16.0.1", @@ -106,7 +106,7 @@ "eslint-plugin-no-relative-import-paths": "^1.5.2", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-react": "^7.37.2", - "eslint-plugin-react-hooks": "^4.6.0" + "eslint-plugin-react-hooks": "^5.0.0" } }, "node_modules/@adobe/css-tools": { @@ -3865,10 +3865,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz", - "integrity": "sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==", - "license": "MIT", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz", + "integrity": "sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==", "engines": { "node": ">=14.0.0" } @@ -7687,13 +7686,12 @@ } }, "node_modules/cypress-multi-reporters": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/cypress-multi-reporters/-/cypress-multi-reporters-1.6.4.tgz", - "integrity": "sha512-3xU2t6pZjZy/ORHaCvci5OT1DAboS4UuMMM8NBAizeb2C9qmHt+cgAjXgurazkwkPRdO7ccK39M5ZaPCju0r6A==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/cypress-multi-reporters/-/cypress-multi-reporters-2.0.4.tgz", + "integrity": "sha512-TZKzSfo8ReU2Fuj1n90gi4Ocw1a/nh6utiq9g0wy27muq1/IjZXdR97WXkV0to2vd8NRldXt+tuKEmxQrp8LDg==", "dev": true, - "license": "MIT", "dependencies": { - "debug": "^4.3.4", + "debug": "^4.3.7", "lodash": "^4.17.21" }, "engines": { @@ -9197,16 +9195,16 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", - "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0.tgz", + "integrity": "sha512-hIOwI+5hYGpJEc4uPRmz2ulCjAGD/N13Lukkh8cLV0i2IRk/bdZDYjgLVHj+U9Z704kLIdIO6iueGvxNur0sgw==", "license": "MIT", "optional": true, "engines": { "node": ">=10" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "node_modules/eslint-plugin-react/node_modules/brace-expansion": { @@ -15934,9 +15932,9 @@ } }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.4.48", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.48.tgz", + "integrity": "sha512-GCRK8F6+Dl7xYniR5a4FYbpBzU8XnZVeowqsQFYdcXuSbChgiks7qybSkbvnaeqv0G0B+dd9/jJgH8kkLDQeEA==", "dev": true, "funding": [ { @@ -15952,10 +15950,9 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { @@ -16932,12 +16929,11 @@ "license": "MIT" }, "node_modules/react-router": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz", - "integrity": "sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==", - "license": "MIT", + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.0.tgz", + "integrity": "sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==", "dependencies": { - "@remix-run/router": "1.20.0" + "@remix-run/router": "1.21.0" }, "engines": { "node": ">=14.0.0" @@ -16947,14 +16943,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.27.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.27.0.tgz", - "integrity": "sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==", + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.0.tgz", + "integrity": "sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==", "dev": true, - "license": "MIT", "dependencies": { - "@remix-run/router": "1.20.0", - "react-router": "6.27.0" + "@remix-run/router": "1.21.0", + "react-router": "6.28.0" }, "engines": { "node": ">=14.0.0" diff --git a/clients/ui/frontend/package.json b/clients/ui/frontend/package.json index dc81ee8bf..12f6ffe55 100644 --- a/clients/ui/frontend/package.json +++ b/clients/ui/frontend/package.json @@ -59,7 +59,7 @@ "cypress-axe": "^1.5.0", "cypress-high-resolution": "^1.0.0", "cypress-mochawesome-reporter": "^3.8.2", - "cypress-multi-reporters": "^1.6.4", + "cypress-multi-reporters": "^2.0.4", "dotenv": "^16.4.5", "dotenv-webpack": "^8.1.0", "expect": "^29.7.0", @@ -69,11 +69,11 @@ "jest-environment-jsdom": "^29.7.0", "junit-report-merger": "^7.0.0", "mini-css-extract-plugin": "^2.9.0", - "postcss": "^8.4.47", + "postcss": "^8.4.48", "prettier": "^3.3.3", "prop-types": "^15.8.1", "raw-loader": "^4.0.2", - "react-router-dom": "^6.26.1", + "react-router-dom": "^6.28.0", "regenerator-runtime": "^0.14.1", "rimraf": "^6.0.1", "sass-loader": "^16.0.1", @@ -125,7 +125,7 @@ "eslint-plugin-no-relative-import-paths": "^1.5.2", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-react": "^7.37.2", - "eslint-plugin-react-hooks": "^4.6.0" + "eslint-plugin-react-hooks": "^5.0.0" }, "overrides": { "serve": { diff --git a/clients/ui/frontend/src/__tests__/cypress/cypress/pages/appChrome.ts b/clients/ui/frontend/src/__tests__/cypress/cypress/pages/appChrome.ts index 8d30c9a48..5d6afc32d 100644 --- a/clients/ui/frontend/src/__tests__/cypress/cypress/pages/appChrome.ts +++ b/clients/ui/frontend/src/__tests__/cypress/cypress/pages/appChrome.ts @@ -9,7 +9,7 @@ class AppChrome { cy.testA11y(); } - // TODO: implement when authorization is enabled + // TODO: [Auth-enablement] Uncomment once auth is enabled // shouldBeUnauthorized() { // cy.findByTestId('unauthorized-error'); // return this; diff --git a/clients/ui/frontend/src/__tests__/cypress/cypress/support/commands/application.ts b/clients/ui/frontend/src/__tests__/cypress/cypress/support/commands/application.ts index 7b5de43ea..c6100d0cd 100644 --- a/clients/ui/frontend/src/__tests__/cypress/cypress/support/commands/application.ts +++ b/clients/ui/frontend/src/__tests__/cypress/cypress/support/commands/application.ts @@ -7,7 +7,7 @@ import type { Matcher, MatcherOptions as DTLMatcherOptions } from '@testing-libr declare global { namespace Cypress { interface Chainable { - // TODO: Uncomment when authorization is enabled + // TODO: [Auth-enablement] Uncomment once auth is enabled // /** // * Visits the URL and performs a login if necessary. // * Uses credentials supplied by environment variables if not provided. @@ -121,7 +121,7 @@ declare global { } } -// TODO: Uncomment when authorization is enabled +// TODO: [Auth-enablement] Uncomment once auth is enabled // Cypress.Commands.add('visitWithLogin', (url, user = TEST_USER) => { // if (Cypress.env('MOCK')) { // cy.visit(url); diff --git a/clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelRegistry/modelRegistry.cy.ts b/clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelRegistry/modelRegistry.cy.ts index dc9940b24..e6cb531c3 100644 --- a/clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelRegistry/modelRegistry.cy.ts +++ b/clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelRegistry/modelRegistry.cy.ts @@ -205,35 +205,22 @@ describe('Model Registry core', () => { }); }); -// TODO: Enable when model registration is there -// describe('Register Model button', () => { -// it('Navigates to register page from empty state', () => { -// initIntercepts({ disableModelRegistryFeature: false, registeredModels: [] }); -// modelRegistry.visit(); -// modelRegistry.findRegisterModelButton().click(); -// cy.findByTestId('app-page-title').should('exist'); -// cy.findByTestId('app-page-title').contains('Register model'); -// cy.findByText('Model registry - modelregistry-sample').should('exist'); -// }); - -// it('Navigates to register page from table toolbar', () => { -// initIntercepts({ disableModelRegistryFeature: false }); -// modelRegistry.visit(); -// modelRegistry.findRegisterModelButton().click(); -// cy.findByTestId('app-page-title').should('exist'); -// cy.findByTestId('app-page-title').contains('Register model'); -// cy.findByText('Model registry - modelregistry-sample').should('exist'); -// }); - -// it('should be accessible for non-admin users', () => { -// asProjectEditUser(); -// initIntercepts({ -// disableModelRegistryFeature: false, -// allowed: false, -// }); - -// modelRegistry.visit(); -// modelRegistry.navigate(); -// modelRegistry.shouldModelRegistrySelectorExist(); -// }); -// }); +describe('Register Model button', () => { + it('Navigates to register page from empty state', () => { + initIntercepts({ registeredModels: [] }); + modelRegistry.visit(); + modelRegistry.findRegisterModelButton().click(); + cy.findByTestId('app-page-title').should('exist'); + cy.findByTestId('app-page-title').contains('Register model'); + cy.findByText('Model registry - modelregistry-sample').should('exist'); + }); + + it('Navigates to register page from table toolbar', () => { + initIntercepts({ registeredModels: [] }); + modelRegistry.visit(); + modelRegistry.findRegisterModelButton().click(); + cy.findByTestId('app-page-title').should('exist'); + cy.findByTestId('app-page-title').contains('Register model'); + cy.findByText('Model registry - modelregistry-sample').should('exist'); + }); +}); diff --git a/clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelVersions.cy.ts b/clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelVersions.cy.ts index 3aa7f9c50..c3fd860e2 100644 --- a/clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelVersions.cy.ts +++ b/clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelVersions.cy.ts @@ -130,7 +130,7 @@ describe('Model Versions', () => { }); it('Model versions table', () => { - // TODO: Uncomment when we fix finding listbox items + // TODO: [Testing] Uncomment when we fix finding listbox items initIntercepts({ modelRegistries: [ diff --git a/clients/ui/frontend/src/app/App.tsx b/clients/ui/frontend/src/app/App.tsx index 430a9ff07..494ee459e 100644 --- a/clients/ui/frontend/src/app/App.tsx +++ b/clients/ui/frontend/src/app/App.tsx @@ -19,11 +19,11 @@ import { StackItem, } from '@patternfly/react-core'; import { BarsIcon } from '@patternfly/react-icons'; -import ToastNotifications from '~/components/ToastNotifications'; +import ToastNotifications from '~/shared/components/ToastNotifications'; +import { useSettings } from '~/shared/hooks/useSettings'; import NavSidebar from './NavSidebar'; import AppRoutes from './AppRoutes'; import { AppContext } from './AppContext'; -import { useSettings } from './useSettings'; import { ModelRegistrySelectorContextProvider } from './context/ModelRegistrySelectorContext'; const App: React.FC = () => { @@ -62,7 +62,7 @@ const App: React.FC = () => {