From 5b92d22f75d43b84058ad6be19910ae0799e5574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Schmelczer?= Date: Sun, 10 Apr 2022 22:25:47 +0200 Subject: [PATCH] Fix charts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: AndrĂ¡s Schmelczer --- .../good_ai/deploy/create_fastapi_app.py | 8 ++++ good_ai/src/good_ai/good_ai/deploy/serve.py | 25 +----------- .../good_ai/good_ai/metrics/assets/index.css | 3 ++ .../good_ai/metrics/create_dash_app.py | 40 +++++++++++++++++-- good_ai/src/good_ai/good_ai/views/sort_by.py | 1 + 5 files changed, 51 insertions(+), 26 deletions(-) create mode 100644 good_ai/src/good_ai/good_ai/metrics/assets/index.css diff --git a/good_ai/src/good_ai/good_ai/deploy/create_fastapi_app.py b/good_ai/src/good_ai/good_ai/deploy/create_fastapi_app.py index 90e6f50..55c03b4 100644 --- a/good_ai/src/good_ai/good_ai/deploy/create_fastapi_app.py +++ b/good_ai/src/good_ai/good_ai/deploy/create_fastapi_app.py @@ -1,9 +1,11 @@ +from pathlib import Path from typing import Any, Dict, List from fastapi import FastAPI, status from fastapi.middleware.wsgi import WSGIMiddleware from fastapi.openapi.docs import get_swagger_ui_html from fastapi.responses import RedirectResponse +from fastapi.staticfiles import StaticFiles from starlette.responses import HTMLResponse from ..context import get_context @@ -11,6 +13,8 @@ from ..helper import snake_case_to_text from ..metrics import create_dash_app from ..views import HealthCheckResponse, Query +PATH = Path(__file__).parent.resolve() + def create_fastapi_app( function_name: str, disable_docs: bool, disable_metrics: bool @@ -40,6 +44,10 @@ def create_fastapi_app( def redirect_to_entrypoint() -> RedirectResponse: return RedirectResponse("/metrics") + app.mount( + "/assets", StaticFiles(directory=PATH / "../metrics/assets"), name="static" + ) + @app.post("/query", status_code=status.HTTP_200_OK) def query_metrics(query: Query) -> List[Dict[str, Any]]: return get_context().persistence.query( diff --git a/good_ai/src/good_ai/good_ai/deploy/serve.py b/good_ai/src/good_ai/good_ai/deploy/serve.py index ec482bd..b489d4d 100644 --- a/good_ai/src/good_ai/good_ai/deploy/serve.py +++ b/good_ai/src/good_ai/good_ai/deploy/serve.py @@ -2,6 +2,7 @@ from typing import Any, Callable import uvicorn from fastapi import FastAPI, status +from uvicorn.config import LOGGING_CONFIG from ..tracing import TracingContext from ..views import Trace @@ -32,8 +33,7 @@ def serve( host="0.0.0.0", port=5050, log_config={ - "version": 1, - "disable_existing_loggers": False, + **LOGGING_CONFIG, "formatters": { "default": { "()": "good_ai.logger.CustomFormatter", @@ -44,26 +44,5 @@ def serve( "fmt": "%(asctime)s | %(levelname)8s | %(message)s", # noqa: E501 }, }, - "handlers": { - "default": { - "formatter": "default", - "class": "logging.StreamHandler", - "stream": "ext://sys.stderr", - }, - "access": { - "formatter": "access", - "class": "logging.StreamHandler", - "stream": "ext://sys.stdout", - }, - }, - "loggers": { - "uvicorn": {"handlers": ["default"], "level": "INFO"}, - "uvicorn.error": {"level": "INFO"}, - "uvicorn.access": { - "handlers": ["access"], - "level": "INFO", - "propagate": False, - }, - }, }, ) diff --git a/good_ai/src/good_ai/good_ai/metrics/assets/index.css b/good_ai/src/good_ai/good_ai/metrics/assets/index.css new file mode 100644 index 0000000..f5469de --- /dev/null +++ b/good_ai/src/good_ai/good_ai/metrics/assets/index.css @@ -0,0 +1,3 @@ +body { + background-color: hotpink; +} \ No newline at end of file diff --git a/good_ai/src/good_ai/good_ai/metrics/create_dash_app.py b/good_ai/src/good_ai/good_ai/metrics/create_dash_app.py index df16f7f..f214e3d 100644 --- a/good_ai/src/good_ai/good_ai/metrics/create_dash_app.py +++ b/good_ai/src/good_ai/good_ai/metrics/create_dash_app.py @@ -7,6 +7,8 @@ from dash import Dash, dash_table, dcc, html from dash.dependencies import Input, Output from flask import Flask +from good_ai.utilities.unique import unique + from ..context import get_context from ..helper import snake_case_to_text from ..views import SortBy @@ -15,10 +17,16 @@ from .get_filter_from_datatable import get_filter_from_datatable def create_dash_app(function_name: str) -> Flask: + flask_app = Flask(__name__) app = Dash( function_name, requests_pathname_prefix=get_context().metrics_path + "/", + server=flask_app, title=snake_case_to_text(function_name), + update_title=None, + external_stylesheets=[ + "/assets/index.css", + ], ) documents = get_context().persistence.get_documents() @@ -117,8 +125,34 @@ def create_dash_app(function_name: str) -> Flask: ) df = pd.DataFrame(rows) - return px.parallel_coordinates( - df, labels={c: snake_case_to_text(c) for c in df.columns} + return go.Figure( + go.Parcoords( + dimensions=[ + get_dimension_descriptor(df, c) + for c in df.columns + if not c.startswith("arg:") and c not in {"id", "created"} + ] + ) ) - return app.server + return flask_app + + +def get_dimension_descriptor(df: pd.DataFrame, column: str) -> Dict[str, Any]: + dimension: Dict[str, Any] = { + "label": snake_case_to_text(column), + } + + values = df[column] + + try: + dimension["values"] = [float(v) for v in values] + except (TypeError, ValueError): + unique_values = unique(values) + value_mapping = {str(v): i for i, v in enumerate(unique_values)} + + dimension["values"] = [value_mapping[str(v)] for v in values] + dimension["tickvals"] = list(value_mapping.values()) + dimension["ticktext"] = list(value_mapping.keys()) + + return dimension diff --git a/good_ai/src/good_ai/good_ai/views/sort_by.py b/good_ai/src/good_ai/good_ai/views/sort_by.py index 5b2b4a9..868000e 100644 --- a/good_ai/src/good_ai/good_ai/views/sort_by.py +++ b/good_ai/src/good_ai/good_ai/views/sort_by.py @@ -1,4 +1,5 @@ from typing import Literal + from typing_extensions import TypedDict