Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 67 additions & 14 deletions extensions/champion-dashboard/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,35 +199,35 @@
.section-content::-webkit-scrollbar-thumb:hover {
background: rgba(55, 53, 47, 0.3);
}
.oauth-association-table {
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.oauth-association-table th {
.data-table th {
background-color: #f7f6f3;
color: #37352f;
font-weight: 600;
padding: 12px;
text-align: left;
border-bottom: 2px solid rgba(55, 53, 47, 0.09);
}
.oauth-association-table td {
.data-table td {
padding: 12px;
color: #37352f;
border-bottom: 1px solid rgba(55, 53, 47, 0.09);
}
.oauth-associatio-table th:first-child {
.data-table th:first-child {
border-right: 2px solid rgba(55, 53, 47, 0.09);
}
.oauth-association-table td:first-child {
.data-table td:first-child {
font-weight: 500;
border-right: 1px solid rgba(55, 53, 47, 0.09);
}
.oauth-association-table td:not(:first-child) {
.data-table td:not(:first-child) {
text-align: center;
}
.oauth-association-table th:not(:first-child) {
.data-table th:not(:first-child) {
text-align: center;
}
.content-table {
Expand Down Expand Up @@ -323,16 +323,31 @@ def fetch_all_prometheus_metrics(url: str) -> Dict[str, List[Tuple[Dict[str, str
print(f"Error fetching metrics from {url}: {e}")
return {}

def get_user_activity_metrics(metrics: Dict) -> Dict[str, Optional[int]]:
def get_user_activity_metrics(metrics: Dict) -> Dict:
"""Parse users_active metric which includes role labels.

Returns both totals (summed across roles) and by-role breakdown.
"""
users_active = metrics.get('users_active', [])
result = {'24h': None, '7d': None, '30d': None, '1y': None}
roles = ['administrator', 'publisher', 'viewer']
windows = ['24h', '7d', '30d', '1y']

by_role = {role: {window: None for window in windows} for role in roles}
totals = {window: 0 for window in windows}

for labels, value in users_active:
role = labels.get('role')
window = labels.get('window')
if window in result:
result[window] = int(value)
if window in windows:
int_value = int(value)
if role in by_role:
by_role[role][window] = int_value
totals[window] += int_value

return result
return {
'totals': totals,
'by_role': by_role
}

def get_content_stats(metrics: Dict) -> Dict:
content_count = metrics.get('content_count', [])
Expand Down Expand Up @@ -579,6 +594,11 @@ def get_server_base_url() -> str:
),
class_="content-card"
),
ui.div(
ui.div("Active Users by Role", class_="card-title"),
ui.output_ui("active_users_by_role"),
class_="content-card"
),
Comment on lines +597 to +601
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great if this actually was hidden if we got back 0s/nones in the data. But maybe that's optimizing for a state we don't ever really expect people to be in.

ui.div(
ui.output_ui("content_stats_grid"),
class_="section-grid-4"
Expand Down Expand Up @@ -610,16 +630,49 @@ def server(input, output, session):
@render.ui
def active_user_stats():
user_metrics = get_user_activity_metrics(metrics)
totals = user_metrics['totals']
stat_labels = [("DAU (24h)", '24h'), ("WAU (7d)", '7d'), ("MAU (30d)", '30d'), ("YAU (1y)", '1y')]
return [
ui.div(
ui.div(label, class_="stat-box-title"),
ui.div(str(user_metrics.get(key) or 0), class_="stat-box-value"),
ui.div(str(totals.get(key) or 0), class_="stat-box-value"),
class_="stat-box"
)
for label, key in stat_labels
]

@output
@render.ui
def active_users_by_role():
user_metrics = get_user_activity_metrics(metrics)
by_role = user_metrics['by_role']
windows = [('24h', 'DAU'), ('7d', 'WAU'), ('30d', 'MAU'), ('1y', 'YAU')]
role_labels = {
'administrator': 'Administrators',
'publisher': 'Publishers',
'viewer': 'Viewers'
}

header_row = ui.tags.tr(
ui.tags.th("Role"),
*[ui.tags.th(label) for _, label in windows]
)

table_rows = []
for role in ['administrator', 'publisher', 'viewer']:
cells = [ui.tags.td(role_labels[role])]
for window, _ in windows:
value = by_role[role].get(window)
display_value = str(value) if value is not None else "0"
cells.append(ui.tags.td(display_value))
table_rows.append(ui.tags.tr(*cells))

return ui.tags.table(
ui.tags.thead(header_row),
ui.tags.tbody(*table_rows),
class_="data-table"
)

@output
@render.ui
def system_info():
Expand Down Expand Up @@ -783,7 +836,7 @@ def create_template_header(template):
return ui.tags.table(
ui.tags.thead(header_row),
ui.tags.tbody(*table_rows),
class_="oauth-association-table"
class_="data-table"
)

@output
Expand Down