Files
shelly-ui/.agents/skills/developing-with-streamlit/templates/apps/dashboard-feature-usage/streamlit_app.py
T
jonas 71803418e5 Initial commit: Shelly Manager with Textual CLI, Streamlit UI, and comprehensive .gitignore
Shelly device management app with mDNS/subnet discovery, inventory,
configuration, and mass operations for Gen1/Gen2+ devices.

Includes .gitignore excluding runtime data (device DB, user config),
AI conversation history, build artifacts, and common Python/OS patterns.
2026-03-23 21:51:59 +01:00

308 lines
10 KiB
Python

"""
API Usage Dashboard Template
A feature analytics dashboard demonstrating:
- Segmented control for category selection
- Multiselect for endpoint filtering
- Starter kits / presets for quick selection
- Time series visualization with normalization
- Metric cards with 28-day deltas
- Rolling average options
This template uses synthetic data. Replace generate_api_data()
with your actual data source (e.g., Snowflake queries, APIs, etc.)
"""
from datetime import date, timedelta
import numpy as np
import pandas as pd
import streamlit as st
import altair as alt
st.set_page_config(
page_title="API Usage Dashboard",
page_icon=":material/api:",
layout="wide",
)
# =============================================================================
# Synthetic Data Generation (Replace with your data source)
# =============================================================================
# API categories and their endpoints
API_CATEGORIES = {
"Users": ["/users", "/users/{id}", "/users/me", "/users/search", "/users/bulk", "/users/export"],
"Orders": ["/orders", "/orders/{id}", "/orders/create", "/orders/cancel", "/orders/refund", "/orders/status"],
"Products": ["/products", "/products/{id}", "/products/search", "/products/categories", "/products/inventory"],
"Analytics": ["/analytics/events", "/analytics/metrics", "/analytics/reports", "/analytics/dashboards"],
}
# Starter kits - predefined endpoint selections
STARTER_KITS = {
"None": [],
"Core CRUD": ["/users", "/users/{id}", "/orders", "/orders/{id}"],
"Search": ["/users/search", "/products/search", "/products/categories"],
"Analytics": ["/analytics/events", "/analytics/metrics", "/analytics/reports"],
"High Volume": ["/users", "/products", "/orders", "/analytics/events"],
}
ROLLING_OPTIONS = {"Raw": 1, "7-day average": 7, "28-day average": 28}
def generate_api_data(
endpoints: list[str],
start_date: date,
end_date: date,
) -> pd.DataFrame:
"""Generate synthetic API usage data.
Replace this function with your actual data source.
"""
np.random.seed(42)
dates = pd.date_range(start=start_date, end=end_date, freq="D")
records = []
for endpoint in endpoints:
# Each endpoint has different base traffic and growth
base = np.random.randint(1000, 50000)
growth = np.random.uniform(0.0005, 0.003)
for i, dt in enumerate(dates):
# Base trend with growth
trend = base * (1 + growth) ** i
# Weekly seasonality (lower on weekends)
if dt.dayofweek >= 5:
trend *= 0.4
# Random noise
value = trend * np.random.uniform(0.85, 1.15)
records.append({
"date": dt,
"endpoint": endpoint,
"request_count": int(value),
})
df = pd.DataFrame(records)
return df
@st.cache_data(ttl=3600)
def load_api_data() -> pd.DataFrame:
"""Load all API usage data."""
end_date = date.today() - timedelta(days=1)
start_date = end_date - timedelta(days=365)
all_endpoints = []
for endpoints in API_CATEGORIES.values():
all_endpoints.extend(endpoints)
return generate_api_data(all_endpoints, start_date, end_date)
def apply_rolling_average(df: pd.DataFrame, window: int) -> pd.DataFrame:
"""Apply rolling average to request data."""
if window == 1:
return df
result = df.copy()
result["request_count"] = (
result.groupby("endpoint")["request_count"]
.transform(lambda x: x.rolling(window, min_periods=1).mean())
)
return result
def normalize_data(df: pd.DataFrame) -> pd.DataFrame:
"""Normalize request counts to percentages (share of total per day)."""
result = df.copy()
daily_totals = result.groupby("date")["request_count"].transform("sum")
result["request_count"] = result["request_count"] / daily_totals
return result
def calculate_delta(df: pd.DataFrame, endpoint: str) -> tuple[float, float | None]:
"""Calculate 28-day delta for an endpoint."""
endpoint_data = df[df["endpoint"] == endpoint].sort_values("date")
if len(endpoint_data) < 2:
return endpoint_data["request_count"].iloc[-1], None
latest = endpoint_data["request_count"].iloc[-1]
if len(endpoint_data) > 28:
previous = endpoint_data["request_count"].iloc[-29]
else:
previous = endpoint_data["request_count"].iloc[0]
delta = latest - previous
return latest, delta
# =============================================================================
# Page Layout
# =============================================================================
# Load data
raw_data = load_api_data()
# Header
st.markdown("# API Usage :material/api:")
st.caption("Select an API category to explore endpoint usage over time.")
# Category selection (not centered)
category = st.segmented_control(
"Select category",
options=[
":material/person: Users",
":material/shopping_cart: Orders",
":material/inventory_2: Products",
":material/analytics: Analytics",
],
default=":material/person: Users",
label_visibility="collapsed",
)
if not category:
st.warning("Please select a category above.", icon=":material/warning:")
st.stop()
# Map display name to category key
category_map = {
":material/person: Users": "Users",
":material/shopping_cart: Orders": "Orders",
":material/inventory_2: Products": "Products",
":material/analytics: Analytics": "Analytics",
}
selected_category = category_map[category]
st.subheader(f"{category} endpoints", divider="gray")
# Layout: filters on left, chart on right
filter_col, chart_col = st.columns([1, 2])
with filter_col:
# Metric selection
with st.expander("Metric", expanded=True, icon=":material/analytics:"):
measure = st.selectbox(
"Choose a measure",
["Request count", "Unique callers", "Error rate"],
index=0,
label_visibility="collapsed",
disabled=True, # Only one option in this template
help="In production, connect to different metrics tables",
)
rolling_label = st.segmented_control(
"Time aggregation",
list(ROLLING_OPTIONS.keys()),
default="7-day average",
label_visibility="collapsed",
)
if rolling_label is None:
st.caption("Please select a time aggregation.")
st.stop()
rolling_window = ROLLING_OPTIONS[rolling_label]
normalize = st.toggle(
"Normalize",
value=False,
help="Normalize to show percentage share of total requests",
)
# Starter kits
with st.expander("Starter kits", expanded=True, icon=":material/auto_awesome:"):
starter_kit = st.pills(
"Quick select",
options=list(STARTER_KITS.keys()),
default="None",
label_visibility="collapsed",
)
# Endpoint selection
available_endpoints = API_CATEGORIES[selected_category]
# Determine default selection based on starter kit
if starter_kit and starter_kit != "None":
default_endpoints = [e for e in STARTER_KITS[starter_kit] if e in available_endpoints]
else:
default_endpoints = available_endpoints[:4] # First 4 endpoints
with st.expander("Endpoints", expanded=True, icon=":material/checklist:"):
selected_endpoints = st.multiselect(
"Select endpoints",
options=available_endpoints,
default=default_endpoints,
label_visibility="collapsed",
)
# Filter and process data
if not selected_endpoints:
with chart_col:
st.info("Select at least one endpoint to view usage data.", icon=":material/info:")
st.stop()
filtered_data = raw_data[raw_data["endpoint"].isin(selected_endpoints)].copy()
filtered_data = apply_rolling_average(filtered_data, rolling_window)
if normalize:
filtered_data = normalize_data(filtered_data)
with chart_col:
# Latest metrics
with st.expander("Latest numbers", expanded=True, icon=":material/numbers:"):
metrics_row = st.container(horizontal=True)
for endpoint in selected_endpoints:
latest, delta = calculate_delta(filtered_data, endpoint)
if normalize:
value_str = f"{latest:.2%}"
delta_str = f"{delta:+.2%}" if delta is not None else None
else:
value_str = f"{latest:,.0f}"
delta_str = f"{delta:+,.0f}" if delta is not None else None
metrics_row.metric(
label=endpoint,
value=value_str,
delta=delta_str,
border=True,
)
# Time series chart
with st.expander("Time series", expanded=True, icon=":material/show_chart:"):
y_format = ".1%" if normalize else ",.0f"
y_title = "Share of requests" if normalize else "Request count"
chart = (
alt.Chart(filtered_data)
.mark_line()
.encode(
x=alt.X("date:T", title="Date"),
y=alt.Y("request_count:Q", title=y_title, axis=alt.Axis(format=y_format)),
color=alt.Color("endpoint:N", title="Endpoint", legend=alt.Legend(orient="bottom")),
tooltip=[
alt.Tooltip("date:T", title="Date", format="%Y-%m-%d"),
alt.Tooltip("endpoint:N", title="Endpoint"),
alt.Tooltip("request_count:Q", title="Requests", format=y_format),
],
)
.properties(height=450)
.interactive()
)
st.altair_chart(chart)
# Raw data section
with st.expander("Raw data", expanded=False, icon=":material/table:"):
display_df = filtered_data.copy()
if normalize:
display_df["request_count"] = display_df["request_count"].apply(lambda x: f"{x:.2%}")
st.dataframe(display_df, hide_index=True)