feat: Improve dashboard functionality and stability
This commit introduces several improvements to the dashboard: - Refactored the component structure for consistency, defining callbacks within the `render` function. - Added robust error handling to data loading and callbacks to prevent crashes. - Implemented linking for SO and AIR columns in the source table. - Added and improved filtering and display options for tables. - Left-aligned columns in tables for better readability. - Cleaned up unused component files.
This commit is contained in:
10
main.py
10
main.py
@ -1,4 +1,3 @@
|
|||||||
# initial commit
|
|
||||||
from dash import Dash
|
from dash import Dash
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
|
|
||||||
@ -20,7 +19,14 @@ def main() -> None:
|
|||||||
print(os.getenv("MY_ENV_VAR"))
|
print(os.getenv("MY_ENV_VAR"))
|
||||||
print(config["Startup"])
|
print(config["Startup"])
|
||||||
# load the data and create the data manager
|
# load the data and create the data manager
|
||||||
data = load_mtbf_data(config["DATA_PATH"])
|
try:
|
||||||
|
data = load_mtbf_data(config["DATA_PATH"])
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Error: Data file not found at {config['DATA_PATH']}")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading data: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
app = Dash(external_stylesheets=[BS])
|
app = Dash(external_stylesheets=[BS])
|
||||||
app.title = "Reliability Dashboard"
|
app.title = "Reliability Dashboard"
|
||||||
|
@ -16,55 +16,55 @@ def render(app: Dash, data: pd.DataFrame) -> html.Div:
|
|||||||
Input(ids.WEEK_DROPDOWN, "value"),
|
Input(ids.WEEK_DROPDOWN, "value"),
|
||||||
Input(ids.PU_HITS_SELECTOR, "value"),
|
Input(ids.PU_HITS_SELECTOR, "value"),
|
||||||
Input(ids.SINGLE_HITTER_FILTER, "value"),
|
Input(ids.SINGLE_HITTER_FILTER, "value"),
|
||||||
|
Input(ids.SYSTEM_FILTER, "value"),
|
||||||
|
Input("span", "value"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def update_bar_chart(
|
def update_bar_chart(
|
||||||
years: list[str], weeks: list[str], u_hits: bool, remove_single: bool
|
year: int, week: int, u_hits: bool, remove_single: bool, systems: list[str], span: int
|
||||||
) -> html.Div:
|
) -> html.Div:
|
||||||
filtered_data = data.query("year in @years and week in @weeks")
|
try:
|
||||||
if filtered_data.shape[0] == 0:
|
if not all([year, week, span]):
|
||||||
return html.Div("No data selected.")
|
return html.Div("No data selected.")
|
||||||
|
|
||||||
hits_col = MTBFSchema.U_HITS if u_hits else MTBFSchema.P_HITS
|
years = [year]
|
||||||
hits_type = "U-Hits" if u_hits else "P-Hits"
|
|
||||||
|
start_week = week - span + 1
|
||||||
|
weeks = list(range(start_week, week + 1))
|
||||||
|
|
||||||
# Count 'Y' values and create pareto
|
filtered_data = data.query("year in @years and Week_Number in @weeks and System in @systems")
|
||||||
pareto_data = (
|
if filtered_data.shape[0] == 0:
|
||||||
filtered_data[filtered_data[hits_col] == "Y"]
|
return html.Div("No data selected.")
|
||||||
.groupby(MTBFSchema.AIR)
|
|
||||||
.size()
|
|
||||||
.reset_index(name="count")
|
|
||||||
)
|
|
||||||
|
|
||||||
if remove_single:
|
hits_col = MTBFSchema.U_HITS if u_hits else MTBFSchema.P_HITS
|
||||||
pareto_data = pareto_data[pareto_data["count"] > 1]
|
hits_type = "U-Hits" if u_hits else "P-Hits"
|
||||||
|
|
||||||
pareto_data = pareto_data.sort_values("count", ascending=False)
|
# Count 'Y' values and create pareto
|
||||||
|
pareto_data = (
|
||||||
|
filtered_data[filtered_data[hits_col] == "Y"]
|
||||||
|
.groupby([MTBFSchema.AIR, MTBFSchema.SYSTEM])
|
||||||
|
.size()
|
||||||
|
.reset_index(name="count")
|
||||||
|
)
|
||||||
|
|
||||||
fig = px.bar(
|
if remove_single:
|
||||||
pareto_data,
|
pareto_data = pareto_data[pareto_data["count"] > 1]
|
||||||
x=MTBFSchema.AIR,
|
|
||||||
y="count",
|
|
||||||
title=f"{hits_type} per AIR (Pareto)",
|
|
||||||
labels={
|
|
||||||
MTBFSchema.AIR: "AIR",
|
|
||||||
"count": "Count",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return dcc.Graph(figure=fig)
|
|
||||||
|
|
||||||
return html.Div(
|
pareto_data = pareto_data.sort_values("count", ascending=False)
|
||||||
children=[
|
|
||||||
dbc.Switch(
|
fig = px.bar(
|
||||||
id=ids.PU_HITS_SELECTOR,
|
pareto_data,
|
||||||
label="P-Hits / U-Hits",
|
x=MTBFSchema.AIR,
|
||||||
value=False,
|
y="count",
|
||||||
),
|
color=MTBFSchema.SYSTEM,
|
||||||
dbc.Switch(
|
title=f"{hits_type} per AIR (Pareto)",
|
||||||
id=ids.SINGLE_HITTER_FILTER,
|
labels={
|
||||||
label="Remove single hitters",
|
MTBFSchema.AIR: "AIR",
|
||||||
value=False,
|
"count": "Count",
|
||||||
),
|
},
|
||||||
html.Div(id=ids.BAR_CHART),
|
)
|
||||||
]
|
return dcc.Graph(figure=fig)
|
||||||
)
|
except Exception as e:
|
||||||
|
return html.Div(f"An error occurred in bar_chart: {e}")
|
||||||
|
|
||||||
|
return html.Div(id=ids.BAR_CHART)
|
||||||
|
@ -26,8 +26,12 @@ def render(app: Dash, data: pd.DataFrame) -> html.Div:
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
def select_all_airs(years: list[str], weeks: list[str], _: int) -> list[str]:
|
def select_all_airs(years: list[str], weeks: list[str], _: int) -> list[str]:
|
||||||
filtered_data = data.query("year in @years and week in @weeks")
|
try:
|
||||||
return sorted(set(filtered_data[MTBFSchema.AIR].tolist()))
|
filtered_data = data.query("year in @years and week in @weeks")
|
||||||
|
return sorted(set(filtered_data[MTBFSchema.AIR].tolist()))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An error occurred in category_dropdown: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
return html.Div(
|
return html.Div(
|
||||||
children=[
|
children=[
|
||||||
|
@ -7,15 +7,6 @@ import dash_bootstrap_components as dbc
|
|||||||
from ..data.loader_gz import MTBFSchema
|
from ..data.loader_gz import MTBFSchema
|
||||||
from . import ids
|
from . import ids
|
||||||
|
|
||||||
import pandas as pd
|
|
||||||
from dash import Dash, dcc, html, dash_table, Input, Output, State, callback_context
|
|
||||||
from datetime import datetime
|
|
||||||
import os
|
|
||||||
import dash_bootstrap_components as dbc
|
|
||||||
|
|
||||||
from ..data.loader_gz import MTBFSchema
|
|
||||||
from . import ids
|
|
||||||
|
|
||||||
def render(app: Dash, data: pd.DataFrame) -> html.Div:
|
def render(app: Dash, data: pd.DataFrame) -> html.Div:
|
||||||
@app.callback(
|
@app.callback(
|
||||||
Output(ids.DATA_TABLE, "children"),
|
Output(ids.DATA_TABLE, "children"),
|
||||||
@ -24,46 +15,68 @@ def render(app: Dash, data: pd.DataFrame) -> html.Div:
|
|||||||
Input(ids.WEEK_DROPDOWN, "value"),
|
Input(ids.WEEK_DROPDOWN, "value"),
|
||||||
Input(ids.PU_HITS_SELECTOR, "value"),
|
Input(ids.PU_HITS_SELECTOR, "value"),
|
||||||
Input(ids.SINGLE_HITTER_FILTER, "value"),
|
Input(ids.SINGLE_HITTER_FILTER, "value"),
|
||||||
|
Input(ids.SYSTEM_FILTER, "value"),
|
||||||
|
Input("span", "value"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def update_data_table(
|
def update_data_table(
|
||||||
years: list[str], weeks: list[str], u_hits: bool, remove_single: bool
|
year: int, week: int, u_hits: bool, remove_single: bool, systems: list[str], span: int
|
||||||
) -> html.Div:
|
) -> html.Div:
|
||||||
filtered_data = data.query(
|
try:
|
||||||
"year in @years and week in @weeks"
|
print(f'year: {year}, week: {week}, span: {span}, systems: {systems}')
|
||||||
)
|
if not all([year, week, span]):
|
||||||
if filtered_data.shape[0] == 0:
|
return html.Div("No data selected.")
|
||||||
return html.Div("No data selected.")
|
|
||||||
|
|
||||||
hits_col = MTBFSchema.U_HITS if u_hits else MTBFSchema.P_HITS
|
years = [year]
|
||||||
|
|
||||||
# Count 'Y' values
|
|
||||||
hits_data = filtered_data[filtered_data[hits_col] == 'Y']
|
|
||||||
|
|
||||||
table_data = hits_data.groupby(MTBFSchema.AIR).agg(
|
|
||||||
count=(hits_col, 'size'),
|
|
||||||
air_issue_description=(MTBFSchema.AIR_ISSUE_DESCRIPTION, lambda x: ', '.join(x.dropna().astype(str).unique())),
|
|
||||||
close_notes=(MTBFSchema.CLOSE_NOTES, lambda x: ', '.join(x.dropna().astype(str).unique()))
|
|
||||||
).reset_index()
|
|
||||||
|
|
||||||
if remove_single:
|
|
||||||
table_data = table_data[table_data['count'] > 1]
|
|
||||||
|
|
||||||
table_data = table_data.sort_values('count', ascending=False)
|
start_week = week - span + 1
|
||||||
|
weeks = list(range(start_week, week + 1))
|
||||||
# Reorder columns
|
|
||||||
table_data = table_data[[MTBFSchema.AIR, 'air_issue_description', 'close_notes', 'count']]
|
|
||||||
|
|
||||||
return dash_table.DataTable(
|
filtered_data = data.query(
|
||||||
id=ids.MTBF_PAR_TABLE, # Using this ID for feedback callbacks
|
"year in @years and Week_Number in @weeks and System in @systems"
|
||||||
data=table_data.to_dict("records"),
|
)
|
||||||
columns=[{"name": i, "id": i} for i in table_data.columns],
|
if filtered_data.shape[0] == 0:
|
||||||
page_size=10,
|
return html.Div("No data selected.")
|
||||||
row_selectable='single',
|
|
||||||
selected_rows=[],
|
hits_col = MTBFSchema.U_HITS if u_hits else MTBFSchema.P_HITS
|
||||||
filter_action='native',
|
|
||||||
sort_action='native'
|
# Count 'Y' values
|
||||||
)
|
hits_data = filtered_data[filtered_data[hits_col] == 'Y']
|
||||||
|
|
||||||
|
table_data = hits_data.groupby([MTBFSchema.AIR, MTBFSchema.SYSTEM]).agg(
|
||||||
|
count=(hits_col, 'size'),
|
||||||
|
air_issue_description=(MTBFSchema.AIR_ISSUE_DESCRIPTION, lambda x: ', '.join(x.dropna().astype(str).unique())),
|
||||||
|
close_notes=(MTBFSchema.CLOSE_NOTES, lambda x: ', '.join(x.dropna().astype(str).unique()))
|
||||||
|
).reset_index()
|
||||||
|
|
||||||
|
if remove_single:
|
||||||
|
table_data = table_data[table_data['count'] > 1]
|
||||||
|
|
||||||
|
table_data['air_issue_description'] = table_data['air_issue_description'].apply(lambda s: s[:80] + '...' if len(s) > 80 else s)
|
||||||
|
|
||||||
|
table_data = table_data.sort_values('count', ascending=False)
|
||||||
|
|
||||||
|
# Reorder columns
|
||||||
|
table_data = table_data[[MTBFSchema.AIR, MTBFSchema.SYSTEM, 'air_issue_description', 'close_notes', 'count']]
|
||||||
|
|
||||||
|
return dash_table.DataTable(
|
||||||
|
id=ids.MTBF_PAR_TABLE, # Using this ID for feedback callbacks
|
||||||
|
data=table_data.to_dict("records"),
|
||||||
|
columns=[{"name": i, "id": i} for i in table_data.columns],
|
||||||
|
page_size=10,
|
||||||
|
row_selectable='single',
|
||||||
|
selected_rows=[],
|
||||||
|
filter_action='native',
|
||||||
|
sort_action='native',
|
||||||
|
style_cell_conditional=[
|
||||||
|
{
|
||||||
|
'if': {'column_id': c},
|
||||||
|
'textAlign': 'left'
|
||||||
|
} for c in ['Close_notes', 'count']
|
||||||
|
]
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
return html.Div(f"An error occurred in data_table: {e}")
|
||||||
|
|
||||||
@app.callback(
|
@app.callback(
|
||||||
Output(ids.FEEDBACK_MODAL, "is_open"),
|
Output(ids.FEEDBACK_MODAL, "is_open"),
|
||||||
|
70
src/components/filter_card.py
Normal file
70
src/components/filter_card.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import dash
|
||||||
|
from dash import dcc, html, Input, Output, State, ctx, ALL
|
||||||
|
import dash_bootstrap_components as dbc
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
import plotly.graph_objects as go
|
||||||
|
import plotly.express as px
|
||||||
|
import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from . import ids
|
||||||
|
|
||||||
|
def render(app: dash.Dash, data: pd.DataFrame) -> dbc.Card:
|
||||||
|
# These values are hardcoded for now, as in main13a.py
|
||||||
|
# They should probably be derived from the data
|
||||||
|
today = datetime.date.today()
|
||||||
|
today_iso = today.isocalendar()
|
||||||
|
default_year = today_iso.year
|
||||||
|
default_week = today_iso.week
|
||||||
|
|
||||||
|
systems = data["System"].unique() if "System" in data.columns else ["GY86", "GY87", "GY88", "GY89"]
|
||||||
|
|
||||||
|
@app.callback(
|
||||||
|
Output(ids.WEEK_DROPDOWN, "options"),
|
||||||
|
Output(ids.WEEK_DROPDOWN, "value"),
|
||||||
|
Input(ids.YEAR_DROPDOWN, "value"),
|
||||||
|
)
|
||||||
|
def update_week_options(year):
|
||||||
|
if not year:
|
||||||
|
return [], None
|
||||||
|
|
||||||
|
if year == today.year:
|
||||||
|
end_week = default_week
|
||||||
|
else:
|
||||||
|
end_week = 52
|
||||||
|
|
||||||
|
week_options = [{"label": f"Week {w}", "value": w} for w in range(1, end_week + 1)]
|
||||||
|
value = end_week
|
||||||
|
return week_options, value
|
||||||
|
|
||||||
|
return dbc.Card([
|
||||||
|
dbc.CardBody([
|
||||||
|
dbc.Row([
|
||||||
|
dbc.Col([dbc.Label("Span"),
|
||||||
|
dcc.Dropdown(id="span",
|
||||||
|
options=[{"label": "4 weeks", "value": 4},
|
||||||
|
{"label": "13 weeks", "value": 13}],
|
||||||
|
value=13, clearable=False)], width=2),
|
||||||
|
dbc.Col([dbc.Label("Year"),
|
||||||
|
dcc.Dropdown(id=ids.YEAR_DROPDOWN,
|
||||||
|
options=[{"label": str(default_year), "value": default_year},
|
||||||
|
{"label": str(default_year-1), "value": default_year-1}],
|
||||||
|
value=default_year, clearable=False)], width=2),
|
||||||
|
dbc.Col([dbc.Label("Last Week"), dcc.Dropdown(id=ids.WEEK_DROPDOWN, clearable=False, value=default_week)], width=2),
|
||||||
|
dbc.Col([dbc.Label("Hit Type"),
|
||||||
|
dbc.Switch(id=ids.PU_HITS_SELECTOR,
|
||||||
|
label="Show u-type (off = p-hit, on = u-type)",
|
||||||
|
value=False)], width=2),
|
||||||
|
dbc.Col([dbc.Label("Filter"),
|
||||||
|
dbc.Switch(id=ids.SINGLE_HITTER_FILTER,
|
||||||
|
label="Remove single hitters",
|
||||||
|
value=False)], width=2),
|
||||||
|
], className="mb-3"),
|
||||||
|
dbc.Row([
|
||||||
|
dbc.Col([dbc.Label("Systems"),
|
||||||
|
dbc.Checklist(id=ids.SYSTEM_FILTER,
|
||||||
|
options=[{"label": system, "value": system} for system in systems],
|
||||||
|
value=systems, inline=True, switch=True)], width=12)
|
||||||
|
])
|
||||||
|
])
|
||||||
|
], className="mb-3")
|
@ -30,3 +30,4 @@ SOURCE_FEEDBACK_LABEL = "source-feedback-label"
|
|||||||
|
|
||||||
PU_HITS_SELECTOR = "pu-hits-selector"
|
PU_HITS_SELECTOR = "pu-hits-selector"
|
||||||
SINGLE_HITTER_FILTER = "single-hitter-filter"
|
SINGLE_HITTER_FILTER = "single-hitter-filter"
|
||||||
|
SYSTEM_FILTER = "system-filter"
|
||||||
|
@ -3,11 +3,10 @@ from dash import Dash, dcc, html
|
|||||||
from src.components import (
|
from src.components import (
|
||||||
bar_chart,
|
bar_chart,
|
||||||
data_table,
|
data_table,
|
||||||
year_dropdown,
|
|
||||||
week_dropdown,
|
|
||||||
feedback_tab,
|
feedback_tab,
|
||||||
explanation_tab,
|
explanation_tab,
|
||||||
source_table_tab,
|
source_table_tab,
|
||||||
|
filter_card,
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_layout(app: Dash, data: pd.DataFrame) -> html.Div:
|
def create_layout(app: Dash, data: pd.DataFrame) -> html.Div:
|
||||||
@ -21,15 +20,9 @@ def create_layout(app: Dash, data: pd.DataFrame) -> html.Div:
|
|||||||
dcc.Tabs(id="tabs", value='tab-dashboard', children=[
|
dcc.Tabs(id="tabs", value='tab-dashboard', children=[
|
||||||
dcc.Tab(label='Dashboard', value='tab-dashboard', children=[
|
dcc.Tab(label='Dashboard', value='tab-dashboard', children=[
|
||||||
html.Div(style=tab_content_style, children=[
|
html.Div(style=tab_content_style, children=[
|
||||||
|
filter_card.render(app, data),
|
||||||
bar_chart.render(app, data),
|
bar_chart.render(app, data),
|
||||||
data_table.render(app, data),
|
data_table.render(app, data),
|
||||||
html.Div(
|
|
||||||
className="dropdown-container",
|
|
||||||
children=[
|
|
||||||
year_dropdown.render(app, data),
|
|
||||||
week_dropdown.render(app, data),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
dcc.Tab(label='Source Table', value='tab-source-table', children=[
|
dcc.Tab(label='Source Table', value='tab-source-table', children=[
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
import pandas as pd
|
|
||||||
import plotly.graph_objects as go
|
|
||||||
from dash import Dash, dcc, html
|
|
||||||
from dash.dependencies import Input, Output
|
|
||||||
|
|
||||||
from ..data.loader import DataSchema
|
|
||||||
from . import ids
|
|
||||||
|
|
||||||
|
|
||||||
def render(app: Dash, data: pd.DataFrame) -> html.Div:
|
|
||||||
@app.callback(
|
|
||||||
Output(ids.PIE_CHART, "children"),
|
|
||||||
[
|
|
||||||
Input(ids.YEAR_DROPDOWN, "value"),
|
|
||||||
Input(ids.MONTH_DROPDOWN, "value"),
|
|
||||||
Input(ids.CATEGORY_DROPDOWN, "value"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def update_pie_chart(
|
|
||||||
years: list[str], months: list[str], categories: list[str]
|
|
||||||
) -> html.Div:
|
|
||||||
filtered_data = data.query(
|
|
||||||
"year in @years and month in @months and category in @categories"
|
|
||||||
)
|
|
||||||
|
|
||||||
if filtered_data.shape[0] == 0:
|
|
||||||
return html.Div("No data selected.", id=ids.PIE_CHART)
|
|
||||||
|
|
||||||
pie = go.Pie(
|
|
||||||
labels=filtered_data[DataSchema.CATEGORY].tolist(),
|
|
||||||
values=filtered_data[DataSchema.AMOUNT].tolist(),
|
|
||||||
hole=0.5,
|
|
||||||
)
|
|
||||||
|
|
||||||
fig = go.Figure(data=[pie])
|
|
||||||
fig.update_layout(margin={"t": 40, "b": 0, "l": 0, "r": 0})
|
|
||||||
fig.update_traces(hovertemplate="%{label}<br>$%{value:.2f}<extra></extra>")
|
|
||||||
|
|
||||||
return html.Div(dcc.Graph(figure=fig), id=ids.PIE_CHART)
|
|
||||||
|
|
||||||
return html.Div(id=ids.PIE_CHART)
|
|
@ -30,7 +30,13 @@ def render(app: Dash, data: pd.DataFrame) -> html.Div:
|
|||||||
|
|
||||||
if triggered_id == ids.SAVE_SOURCE_FEEDBACK_BUTTON and active_cell:
|
if triggered_id == ids.SAVE_SOURCE_FEEDBACK_BUTTON and active_cell:
|
||||||
selected_row_data = table_data[active_cell["row"]]
|
selected_row_data = table_data[active_cell["row"]]
|
||||||
so_number = selected_row_data[MTBFSchema.SO]
|
so_markdown = selected_row_data[MTBFSchema.SO]
|
||||||
|
|
||||||
|
if so_markdown and so_markdown.startswith('[') and ']' in so_markdown:
|
||||||
|
so_number = so_markdown[so_markdown.find('[')+1:so_markdown.find(']')]
|
||||||
|
else:
|
||||||
|
so_number = so_markdown
|
||||||
|
|
||||||
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
if not comment:
|
if not comment:
|
||||||
comment = ""
|
comment = ""
|
||||||
@ -60,7 +66,11 @@ def render(app: Dash, data: pd.DataFrame) -> html.Div:
|
|||||||
)
|
)
|
||||||
def update_feedback_so_label(active_cell, table_data):
|
def update_feedback_so_label(active_cell, table_data):
|
||||||
if active_cell:
|
if active_cell:
|
||||||
so_number = table_data[active_cell["row"]][MTBFSchema.SO]
|
so_markdown = table_data[active_cell["row"]][MTBFSchema.SO]
|
||||||
|
if so_markdown and so_markdown.startswith('[') and ']' in so_markdown:
|
||||||
|
so_number = so_markdown[so_markdown.find('[')+1:so_markdown.find(']')]
|
||||||
|
else:
|
||||||
|
so_number = so_markdown
|
||||||
return f"SO Number: {so_number}"
|
return f"SO Number: {so_number}"
|
||||||
return "SO Number: "
|
return "SO Number: "
|
||||||
|
|
||||||
@ -80,15 +90,42 @@ def render(app: Dash, data: pd.DataFrame) -> html.Div:
|
|||||||
is_open=False,
|
is_open=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
desired_columns = ['System', 'Customer', 'Root_Error', 'Year_Week_Number', 'SDT_TimeStamp', 'SO', 'AIR', 'Air_issue_description', 'Close_notes']
|
||||||
|
existing_columns = [col for col in desired_columns if col in data.columns]
|
||||||
|
|
||||||
|
table_data = data[existing_columns].copy()
|
||||||
|
|
||||||
|
so_base_url = os.getenv('HTTP_SO_ADDR', '')
|
||||||
|
air_base_url = os.getenv('HTTP_AIR_ADDR', '')
|
||||||
|
|
||||||
|
if so_base_url:
|
||||||
|
table_data['SO'] = table_data['SO'].apply(lambda so: f'[{so}]({so_base_url}{so})' if so else '')
|
||||||
|
if air_base_url:
|
||||||
|
table_data['AIR'] = table_data['AIR'].apply(lambda air: f'[{air}]({air_base_url}{air})' if air else '')
|
||||||
|
|
||||||
|
columns_config = []
|
||||||
|
for col in existing_columns:
|
||||||
|
if col in ['SO', 'AIR']:
|
||||||
|
columns_config.append({"name": col, "id": col, "presentation": "markdown"})
|
||||||
|
else:
|
||||||
|
columns_config.append({"name": col, "id": col})
|
||||||
|
|
||||||
|
|
||||||
return html.Div([
|
return html.Div([
|
||||||
dash_table.DataTable(
|
dash_table.DataTable(
|
||||||
id=ids.SOURCE_TABLE,
|
id=ids.SOURCE_TABLE,
|
||||||
data=data.to_dict("records"),
|
data=table_data.to_dict("records"),
|
||||||
columns=[{"name": i, "id": i} for i in data.columns],
|
columns=columns_config,
|
||||||
page_size=15,
|
page_size=15,
|
||||||
sort_action="native",
|
sort_action="native",
|
||||||
filter_action="native",
|
filter_action="native",
|
||||||
cell_selectable=True,
|
cell_selectable=True,
|
||||||
|
style_cell_conditional=[
|
||||||
|
{
|
||||||
|
'if': {'column_id': c},
|
||||||
|
'textAlign': 'left'
|
||||||
|
} for c in ['Air_issue_description', 'Close_notes']
|
||||||
|
]
|
||||||
),
|
),
|
||||||
html.Div(id=ids.SOURCE_FEEDBACK_MESSAGE),
|
html.Div(id=ids.SOURCE_FEEDBACK_MESSAGE),
|
||||||
modal
|
modal
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
import pandas as pd
|
|
||||||
from dash import Dash, dcc, html
|
|
||||||
from dash.dependencies import Input, Output
|
|
||||||
|
|
||||||
from ..data.loader import DataSchema
|
|
||||||
from . import ids
|
|
||||||
|
|
||||||
def render(app: Dash, data: pd.DataFrame) -> html.Div:
|
|
||||||
all_weeks: list[str] = data[DataSchema.WEEK].tolist()
|
|
||||||
unique_weeks = sorted(list(set(all_weeks)), reverse=True)
|
|
||||||
default_weeks = unique_weeks[:13]
|
|
||||||
|
|
||||||
@app.callback(
|
|
||||||
Output(ids.WEEK_DROPDOWN, "value"),
|
|
||||||
[
|
|
||||||
Input(ids.YEAR_DROPDOWN, "value"),
|
|
||||||
Input(ids.SELECT_ALL_WEEKS_BUTTON, "n_clicks"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def select_all_weeks(years: list[str], _: int) -> list[str]:
|
|
||||||
filtered_data = data.query("year in @years")
|
|
||||||
return sorted(set(filtered_data[DataSchema.WEEK].tolist()))
|
|
||||||
|
|
||||||
return html.Div(
|
|
||||||
children=[
|
|
||||||
html.H6("Week"),
|
|
||||||
dcc.Dropdown(
|
|
||||||
id=ids.WEEK_DROPDOWN,
|
|
||||||
options=[{"label": f"{week[:4]}-{week[4:]}", "value": week} for week in unique_weeks],
|
|
||||||
value=default_weeks,
|
|
||||||
multi=True,
|
|
||||||
),
|
|
||||||
html.Button(
|
|
||||||
className="dropdown-button",
|
|
||||||
children=["Select All"],
|
|
||||||
id=ids.SELECT_ALL_WEEKS_BUTTON,
|
|
||||||
n_clicks=0,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
@ -1,36 +0,0 @@
|
|||||||
import pandas as pd
|
|
||||||
from dash import Dash, dcc, html
|
|
||||||
from dash.dependencies import Input, Output
|
|
||||||
|
|
||||||
from ..data.loader import DataSchema
|
|
||||||
from . import ids
|
|
||||||
|
|
||||||
|
|
||||||
def render(app: Dash, data: pd.DataFrame) -> html.Div:
|
|
||||||
all_years: list[str] = data[DataSchema.YEAR].tolist()
|
|
||||||
unique_years = sorted(set(all_years), key=int)
|
|
||||||
|
|
||||||
@app.callback(
|
|
||||||
Output(ids.YEAR_DROPDOWN, "value"),
|
|
||||||
Input(ids.SELECT_ALL_YEARS_BUTTON, "n_clicks"),
|
|
||||||
)
|
|
||||||
def select_all_years(_: int) -> list[str]:
|
|
||||||
return unique_years
|
|
||||||
|
|
||||||
return html.Div(
|
|
||||||
children=[
|
|
||||||
html.H6("Year"),
|
|
||||||
dcc.Dropdown(
|
|
||||||
id=ids.YEAR_DROPDOWN,
|
|
||||||
options=[{"label": year, "value": year} for year in unique_years],
|
|
||||||
value=unique_years,
|
|
||||||
multi=True,
|
|
||||||
),
|
|
||||||
html.Button(
|
|
||||||
className="dropdown-button",
|
|
||||||
children=["Select All"],
|
|
||||||
id=ids.SELECT_ALL_YEARS_BUTTON,
|
|
||||||
n_clicks=0,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
Reference in New Issue
Block a user