Skip to content

Commit

Permalink
Kedro Viz Lite User Warning UI (#2092)
Browse files Browse the repository at this point in the history
* sync remote

* add lite viz banner

* banner design change

* add tests

* refactor metadata api

* fix permissions

* working state

* comment out banner condition for testing

* address PR comments

* fix tests

* update tests

* update release
  • Loading branch information
ravi-kumar-pilla committed Sep 17, 2024
1 parent 28a2fd0 commit 30d5cef
Show file tree
Hide file tree
Showing 30 changed files with 744 additions and 190 deletions.
1 change: 1 addition & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Please follow the established format:
- Fixes design issues in metadata panel. (#2009)
- Fix missing run command in metadata panel for task nodes. (#2055)
- Add `UnavailableDataset` as a default dataset for `--lite` mode. (#2083)
- Add `kedro viz --lite` user warning banner UI. (#2092)

# Release 9.2.0

Expand Down
15 changes: 15 additions & 0 deletions cypress/fixtures/mock/compatibleMetadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"has_missing_dependencies": false,
"package_compatibilities": [
{
"package_name": "fsspec",
"package_version": "2023.9.1",
"is_compatible": true
},
{
"package_name": "kedro-datasets",
"package_version": "2.0.0",
"is_compatible": true
}
]
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
[
{
{
"has_missing_dependencies": true,
"package_compatibilities": [
{
"package_name": "fsspec",
"package_version": "2023.8.1",
"is_compatible": false
},
{
},
{
"package_name": "kedro-datasets",
"package_version": "1.8.0",
"is_compatible": false
}
]
}
]
}
12 changes: 0 additions & 12 deletions cypress/fixtures/mock/package-compatibilities-compatible.json

This file was deleted.

32 changes: 32 additions & 0 deletions cypress/tests/ui/flowchart/banners.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
describe('Banners in Kedro-Viz', () => {
beforeEach(() => {
// Clears localStorage before each test
cy.clearLocalStorage();
});

it("shows a missing dependencies banner in viz lite mode if the kedro project dependencies are not installed.", () => {
// Intercept the network request to mock with a fixture
cy.__interceptRest__(
'/api/metadata',
'GET',
'/mock/inCompatibleMetadata.json'
).as("appMetadata");

// Action
cy.reload();

// Assert after action
cy.get('[data-test="flowchart-wrapper--lite-banner"]').should('exist');
cy.get('.banner-message-body').should('contains.text', 'please install the missing Kedro project dependencies')
cy.get('.banner-message-title').should('contains.text', 'Missing dependencies')

// Test Learn more link
cy.get(".banner a")
.should("contains.attr", "href", "https://docs.kedro.org/projects/kedro-viz/en/latest/kedro-viz_visualisation.html#visualise-a-kedro-project-without-installing-project-dependencies");

// Close the banner
cy.get(".banner-close").click()
cy.get('[data-test="flowchart-wrapper--lite-banner"]').should('not.exist');

});
});
8 changes: 4 additions & 4 deletions cypress/tests/ui/flowchart/shareable-urls.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ describe('Shareable URLs with empty localStorage', () => {
it('verifies that users can open the Deploy Kedro-Viz modal if the localStorage is empty. #TC-52', () => {
// Intercept the network request to mock with a fixture
cy.__interceptRest__(
'/api/package-compatibilities',
'/api/metadata',
'GET',
'/mock/package-compatibilities-compatible.json'
'/mock/compatibleMetadata.json'
);

// Action
Expand All @@ -25,9 +25,9 @@ describe('Shareable URLs with empty localStorage', () => {
it("shows an incompatible message given the user's fsspec package version is outdated. #TC-53", () => {
// Intercept the network request to mock with a fixture
cy.__interceptRest__(
'/api/package-compatibilities',
'/api/metadata',
'GET',
'/mock/package-compatibilities-incompatible.json'
'/mock/inCompatibleMetadata.json'
);

// Action
Expand Down
62 changes: 23 additions & 39 deletions package/kedro_viz/api/rest/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@
import abc
import json
import logging
from importlib.metadata import PackageNotFoundError
from typing import Any, Dict, List, Optional, Union

import orjson
import packaging
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse, ORJSONResponse
from pydantic import BaseModel, ConfigDict

from kedro_viz.api.rest.utils import get_package_version
from kedro_viz.api.rest.utils import get_package_compatibilities
from kedro_viz.data_access import data_access_manager
from kedro_viz.models.flowchart import (
DataNode,
Expand All @@ -24,6 +22,7 @@
TranscodedDataNode,
TranscodedDataNodeMetadata,
)
from kedro_viz.models.metadata import Metadata, PackageCompatibility

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -259,17 +258,24 @@ class GraphAPIResponse(BaseAPIResponse):
selected_pipeline: str


class PackageCompatibilityAPIResponse(BaseAPIResponse):
package_name: str
package_version: str
is_compatible: bool
class MetadataAPIResponse(BaseAPIResponse):
has_missing_dependencies: bool = False
package_compatibilities: List[PackageCompatibility] = []
model_config = ConfigDict(
json_schema_extra={
"example": {
"package_name": "fsspec",
"package_version": "2023.9.1",
"is_compatible": True,
}
"has_missing_dependencies": False,
"package_compatibilities": [
{
"package_name": "fsspec",
"package_version": "2024.6.1",
"is_compatible": True,
},
{
"package_name": "kedro-datasets",
"package_version": "4.0.0",
"is_compatible": True,
},
],
}
)

Expand Down Expand Up @@ -372,33 +378,11 @@ def get_selected_pipeline_response(registered_pipeline_id: str):
)


def get_package_compatibilities_response(
package_requirements: Dict[str, Dict[str, str]],
) -> List[PackageCompatibilityAPIResponse]:
"""API response for `/api/package_compatibility`."""
package_requirements_response = []

for package_name, package_info in package_requirements.items():
compatible_version = package_info["min_compatible_version"]
try:
package_version = get_package_version(package_name)
except PackageNotFoundError:
logger.warning(package_info["warning_message"])
package_version = "0.0.0"

is_compatible = packaging.version.parse(
package_version
) >= packaging.version.parse(compatible_version)

package_requirements_response.append(
PackageCompatibilityAPIResponse(
package_name=package_name,
package_version=package_version,
is_compatible=is_compatible,
)
)

return package_requirements_response
def get_metadata_response():
"""API response for `/api/metadata`."""
package_compatibilities = get_package_compatibilities()
Metadata.set_package_compatibilities(package_compatibilities)
return Metadata()


def get_encoded_response(response: Any) -> bytes:
Expand Down
20 changes: 8 additions & 12 deletions package/kedro_viz/api/rest/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,21 @@

# pylint: disable=missing-function-docstring, broad-exception-caught
import logging
from typing import List

from fastapi import APIRouter
from fastapi.responses import JSONResponse

from kedro_viz.api.rest.requests import DeployerConfiguration
from kedro_viz.constants import PACKAGE_REQUIREMENTS
from kedro_viz.integrations.deployment.deployer_factory import DeployerFactory

from .responses import (
APIErrorMessage,
GraphAPIResponse,
MetadataAPIResponse,
NodeMetadataAPIResponse,
PackageCompatibilityAPIResponse,
get_default_response,
get_metadata_response,
get_node_metadata_response,
get_package_compatibilities_response,
get_selected_pipeline_response,
)

Expand Down Expand Up @@ -91,17 +89,15 @@ async def deploy_kedro_viz(input_values: DeployerConfiguration):


@router.get(
"/package-compatibilities",
response_model=List[PackageCompatibilityAPIResponse],
"/metadata",
response_model=MetadataAPIResponse,
)
async def get_package_compatibilities():
async def get_metadata():
try:
return get_package_compatibilities_response(PACKAGE_REQUIREMENTS)
return get_metadata_response()
except Exception as exc:
logger.exception(
"An exception occurred while getting package compatibility info : %s", exc
)
logger.exception("An exception occurred while getting app metadata: %s", exc)
return JSONResponse(
status_code=500,
content={"message": "Failed to get package compatibility info"},
content={"message": "Failed to get app metadata"},
)
39 changes: 39 additions & 0 deletions package/kedro_viz/api/rest/utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,49 @@
"""`kedro_viz.api.rest.utils` contains utility functions used in the `kedro_viz.api.rest` package"""

import logging
from importlib.metadata import PackageNotFoundError
from typing import List

import packaging

from kedro_viz.constants import PACKAGE_REQUIREMENTS
from kedro_viz.models.metadata import PackageCompatibility

try:
from importlib.metadata import version
except ImportError: # pragma: no cover
from importlib_metadata import version

logger = logging.getLogger(__name__)


def get_package_version(package_name: str):
"""Returns the version of the given package."""
return version(package_name) # pragma: no cover


def get_package_compatibilities() -> List[PackageCompatibility]:
"""Returns the package compatibilities information
for the current python env."""
package_compatibilities: List[PackageCompatibility] = []

for package_name, package_info in PACKAGE_REQUIREMENTS.items():
compatible_version = package_info["min_compatible_version"]
try:
package_version = get_package_version(package_name)
except PackageNotFoundError:
logger.warning(package_info["warning_message"])
package_version = "0.0.0"

is_compatible = packaging.version.parse(
package_version
) >= packaging.version.parse(compatible_version)

package_compatibilities.append(
PackageCompatibility(
package_name=package_name,
package_version=package_version,
is_compatible=is_compatible,
)
)
return package_compatibilities
4 changes: 4 additions & 0 deletions package/kedro_viz/integrations/kedro/data_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from kedro_viz.integrations.kedro.abstract_dataset_lite import AbstractDatasetLite
from kedro_viz.integrations.kedro.lite_parser import LiteParser
from kedro_viz.integrations.utils import _VizNullPluginManager
from kedro_viz.models.metadata import Metadata

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -145,6 +146,9 @@ def load_data(
if unresolved_imports and len(unresolved_imports) > 0:
modules_to_mock: Set[str] = set()

# for the viz lite banner
Metadata.set_has_missing_dependencies(True)

for unresolved_module_set in unresolved_imports.values():
modules_to_mock = modules_to_mock.union(unresolved_module_set)

Expand Down
47 changes: 47 additions & 0 deletions package/kedro_viz/models/metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""`kedro_viz.models.metadata` defines metadata for Kedro-Viz application."""

# pylint: disable=missing-function-docstring
from typing import ClassVar, List

from pydantic import BaseModel, field_validator


class PackageCompatibility(BaseModel):
"""Represent package compatibility in app metadata"""

package_name: str
package_version: str
is_compatible: bool

@field_validator("package_name")
@classmethod
def set_package_name(cls, value):
assert isinstance(value, str)
return value

@field_validator("package_version")
@classmethod
def set_package_version(cls, value):
assert isinstance(value, str)
return value

@field_validator("is_compatible")
@classmethod
def set_is_compatible(cls, value):
assert isinstance(value, bool)
return value


class Metadata(BaseModel):
"""Represent Kedro-Viz application metadata"""

has_missing_dependencies: ClassVar[bool] = False
package_compatibilities: ClassVar[List[PackageCompatibility]] = []

@classmethod
def set_package_compatibilities(cls, value: List[PackageCompatibility]):
cls.package_compatibilities = value

@classmethod
def set_has_missing_dependencies(cls, value: bool):
cls.has_missing_dependencies = value
Loading

0 comments on commit 30d5cef

Please sign in to comment.