UX improvements

Signed-off-by: András Schmelczer <andras@schmelczer.dev>
This commit is contained in:
Andras Schmelczer 2022-04-12 20:58:00 +02:00
parent 498c7f0459
commit 106ef5cc3f
8 changed files with 217 additions and 26 deletions

View file

@ -1,2 +1,3 @@
from .get_args import get_args from .get_args import get_args
from .snake_case_to_text import snake_case_to_text from .snake_case_to_text import snake_case_to_text
from .text_to_hex_color import text_to_hex_color

View file

@ -0,0 +1,13 @@
import colorsys
from hashlib import md5
def text_to_hex_color(text: str) -> str:
ascii_bytes = text.encode("ascii")
digest = md5(
ascii_bytes
).hexdigest() # the built-in hash function is salted differently in each process
integer = int(digest, 16)
hue = integer % 6311 / 6311.0
rgb = colorsys.hsv_to_rgb(hue, 0.75, 0.6)
return "#" + "".join("%02X" % round(i * 255) for i in rgb)

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -1,3 +1,112 @@
body { :root {
background-color: hotpink; --accent-color: #47c2d0;
--error-color: #a30808;
--background-color: #edf5f6;
--small-padding: 10px;
--medium-padding: 20px;
--large-padding: 40px;
--border-radius: 10px;
--shadow: 0 4px 6px -1px rgb(0 0 0 / 10%), 0 2px 4px -1px rgb(0 0 0 / 6%);
}
* {
margin: 0;
box-sizing: border-box;
}
body {
background-color: var(--background-color);
font-family: Arial, Helvetica, sans-serif;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: var(--medium-padding) 0 var(--small-padding) 0;
}
.glance,
.table-container,
.parallel-coords {
padding: var(--large-padding);
margin: var(--large-padding);
border-radius: var(--border-radius);
box-shadow: var(--shadow);
background-color: white;
overflow: hidden;
}
.glance {
display: flex;
}
.glance .description {
width: 350px;
display: flex;
flex-direction: column;
justify-content: center;
}
.glance .dash-graph {
flex: 1;
}
.table-container {
padding: 0;
}
.table-container h2 {
padding: var(--small-padding);
margin: 0;
}
.table-container h2,
footer.watermark {
opacity: 0.35;
}
.dash-spreadsheet {
overflow: auto;
}
th,
td {
max-width: 350px;
}
.parallel-coords {
padding: 0;
}
footer.watermark {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--large-padding);
background-color: #ddd;
position: relative;
}
footer.watermark div {
display: flex;
flex-direction: column;
}
h6 {
font-size: 3rem;
margin-top: 0;
}
a img {
width: 64px;
height: 64px;
cursor: pointer;
transition: transform 300ms;
}
a img:hover {
transform: scale(1.1);
} }

View file

@ -10,13 +10,16 @@ from flask import Flask
from good_ai.utilities.unique import unique from good_ai.utilities.unique import unique
from ..context import get_context from ..context import get_context
from ..helper import snake_case_to_text from ..helper import snake_case_to_text, text_to_hex_color
from ..views import SortBy from ..views import SortBy
from .get_description import get_description from .get_description import get_description
from .get_filter_from_datatable import get_filter_from_datatable from .get_filter_from_datatable import get_filter_from_datatable
from .get_footer import get_footer
def create_dash_app(function_name: str) -> Flask: def create_dash_app(function_name: str) -> Flask:
accent_color = text_to_hex_color(function_name)
flask_app = Flask(__name__) flask_app = Flask(__name__)
app = Dash( app = Dash(
function_name, function_name,
@ -32,11 +35,8 @@ def create_dash_app(function_name: str) -> Flask:
documents = get_context().persistence.get_documents() documents = get_context().persistence.get_documents()
df = pd.DataFrame(documents) df = pd.DataFrame(documents)
app.layout = html.Div( execution_time_histogram = dcc.Graph(config={"displaylogo": False})
[ table = dash_table.DataTable(
get_description(function_name),
html.Div(
table := dash_table.DataTable(
columns=[{"name": i, "id": i} for i in df.columns], columns=[{"name": i, "id": i} for i in df.columns],
page_current=0, page_current=0,
page_size=20, page_size=20,
@ -45,11 +45,27 @@ def create_dash_app(function_name: str) -> Flask:
filter_query="", filter_query="",
sort_action="custom", sort_action="custom",
sort_mode="multi", sort_mode="multi",
sort_by=[], sort_by=[
{"column_id": "created", "direction": "desc"},
],
)
app.layout = html.Div(
[
html.Div(
[
get_description(
function_name=function_name, accent_color=accent_color
), ),
execution_time_histogram,
],
className="glance",
), ),
execution_time_histogram := dcc.Graph(), html.Div([html.H2("Latest traces"), table], className="table-container"),
parallel_coords := dcc.Graph(), parallel_coords := dcc.Graph(
className="parallel-coords", config={"displaylogo": False}
),
get_footer(),
interval := dcc.Interval( interval := dcc.Interval(
interval=4 * 1000, # in milliseconds interval=4 * 1000, # in milliseconds
n_intervals=0, n_intervals=0,
@ -100,15 +116,23 @@ def create_dash_app(function_name: str) -> Flask:
) )
df = pd.DataFrame(rows) df = pd.DataFrame(rows)
return px.histogram( fig = px.histogram(
df, df,
x="execution_time_ms", x="execution_time_ms",
labels={"execution_time_ms": "Execution time (ms)"}, labels={"execution_time_ms": "Execution time (ms)"},
nbins=20, nbins=20,
title="Execution times", height=400,
log_y=True, log_y=True,
color_discrete_sequence=[accent_color],
) )
fig.update_layout(
autosize=True,
margin=dict(l=0, r=0, b=0, t=0, pad=0),
)
return fig
@app.callback( @app.callback(
Output(parallel_coords, "figure"), Output(parallel_coords, "figure"),
Input(table, "filter_query"), Input(table, "filter_query"),
@ -131,7 +155,8 @@ def create_dash_app(function_name: str) -> Flask:
get_dimension_descriptor(df, c) get_dimension_descriptor(df, c)
for c in df.columns for c in df.columns
if not c.startswith("arg:") and c not in {"id", "created"} if not c.startswith("arg:") and c not in {"id", "created"}
] ],
line_color=accent_color,
) )
) )

View file

@ -1,12 +1,11 @@
from dash.dcc import Markdown from dash import dcc, html
from ..helper import snake_case_to_text from ..helper import snake_case_to_text
def get_description(function_name: str) -> Markdown: def get_description(function_name: str, accent_color: str) -> html.Div:
markdown_text = f""" markdown_text = f"""
# {snake_case_to_text(function_name)} - metrics View the live data of your deployments here.
> A human-friendly framework for robust end-to-end AI deployments
## Using the API ## Using the API
@ -14,7 +13,15 @@ def get_description(function_name: str) -> Markdown:
## Metrics ## Metrics
The recent traces and aggregated metrics are presented below. Recent traces and aggregated metrics are presented below. Try filtering the table.
""" """
return Markdown(markdown_text) return html.Div(
[
html.H1(
f"{snake_case_to_text(function_name)} - metrics",
style={"color": accent_color},
),
dcc.Markdown(markdown_text, className="description"),
]
)

View file

@ -0,0 +1,22 @@
from dash import html
def get_footer() -> html.Footer:
return html.Footer(
[
html.Div(
[
html.H6("GoodAI"),
html.P(
"A human-friendly framework for robust end-to-end AI deployments."
),
]
),
html.A(
html.Img(src="/assets/github.png"),
href="https://github.com/ScoutinScience/good-ai",
target="_blank",
),
],
className="watermark",
)

View file

@ -11,3 +11,17 @@ class Query(BaseModel):
sort: List[SortBy] = [] sort: List[SortBy] = []
skip: int = 0 skip: int = 0
take: int = 100 take: int = 100
class Config:
schema_extra = {
"example": {
"filter": [
{"property": "execution_time_ms", "operator": ">", "value": 100}
],
"sort": [
{"column_id": "execution_time_ms", "direction": "asc"},
{"column_id": "id", "direction": "desc"},
],
"take": 10,
}
}