Skip to content

Commit 14a6917

Browse files
athul-rsclaude
andcommitted
UN-1798: Fix dashboard metrics - HITL, pages processed, sidebar nav
- Fix HITL import path: manual_review_v2 → pluggable_apps.manual_review_v2 - Fix HITL model: ManualReviewEntity → HITLQueue with _base_manager - Fix pages_processed: resolve org string identifier from UUID for PageUsage - Add HITL metrics to live_series endpoint and summary - Restore metrics dashboard sidebar nav removed by sidebar refactor (#1768) Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent e83a1a2 commit 14a6917

File tree

7 files changed

+122
-16
lines changed

7 files changed

+122
-16
lines changed

backend/dashboard_metrics/services.py

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@
2222

2323
from dashboard_metrics.models import Granularity
2424
from unstract.core.data_models import ExecutionStatus
25+
from account_v2.models import Organization
26+
27+
28+
def _get_hitl_queue_model():
29+
"""Get HITLQueue model if available (cloud-only).
30+
31+
Returns None on OSS where manual_review_v2 is not installed.
32+
"""
33+
try:
34+
from pluggable_apps.manual_review_v2.models import HITLQueue
35+
36+
return HITLQueue
37+
except ImportError:
38+
return None
2539

2640

2741
def _get_usage_queryset():
@@ -109,6 +123,10 @@ def get_pages_processed(
109123
110124
Sums pages_processed field grouped by time period.
111125
126+
Note: PageUsage.organization_id stores the Organization's string
127+
identifier (organization.organization_id), NOT the UUID PK.
128+
We look up the string identifier from the UUID passed by callers.
129+
112130
Args:
113131
organization_id: Organization UUID string
114132
start_date: Start of date range
@@ -118,11 +136,17 @@ def get_pages_processed(
118136
Returns:
119137
List of dicts with 'period' and 'value' keys
120138
"""
139+
try:
140+
org = Organization.objects.get(id=organization_id)
141+
org_identifier = org.organization_id
142+
except Organization.DoesNotExist:
143+
return []
144+
121145
trunc_func = MetricsQueryService._get_trunc_func(granularity)
122146

123147
return list(
124148
PageUsage.objects.filter(
125-
organization_id=organization_id,
149+
organization_id=org_identifier,
126150
created_at__gte=start_date,
127151
created_at__lte=end_date,
128152
)
@@ -457,6 +481,9 @@ def get_hitl_reviews(
457481
) -> list[dict[str, Any]]:
458482
"""Query HITL review counts from manual_review_v2.
459483
484+
Counts all HITLQueue records created in the date range,
485+
regardless of their current state.
486+
460487
Returns empty list on OSS where manual_review_v2 is not installed.
461488
462489
Args:
@@ -468,15 +495,14 @@ def get_hitl_reviews(
468495
Returns:
469496
List of dicts with 'period' and 'value' keys
470497
"""
471-
try:
472-
from manual_review_v2.models import ManualReviewEntity
473-
except ImportError:
498+
HITLQueue = _get_hitl_queue_model()
499+
if HITLQueue is None:
474500
return []
475501

476502
trunc_func = MetricsQueryService._get_trunc_func(granularity)
477503

478504
return list(
479-
ManualReviewEntity.objects.filter(
505+
HITLQueue._base_manager.filter(
480506
organization_id=organization_id,
481507
created_at__gte=start_date,
482508
created_at__lte=end_date,
@@ -496,6 +522,9 @@ def get_hitl_completions(
496522
) -> list[dict[str, Any]]:
497523
"""Query completed HITL reviews from manual_review_v2.
498524
525+
Counts HITLQueue records with state='approved' that were approved
526+
within the date range (using approved_at timestamp).
527+
499528
Returns empty list on OSS where manual_review_v2 is not installed.
500529
501530
Args:
@@ -507,21 +536,20 @@ def get_hitl_completions(
507536
Returns:
508537
List of dicts with 'period' and 'value' keys
509538
"""
510-
try:
511-
from manual_review_v2.models import ManualReviewEntity
512-
except ImportError:
539+
HITLQueue = _get_hitl_queue_model()
540+
if HITLQueue is None:
513541
return []
514542

515543
trunc_func = MetricsQueryService._get_trunc_func(granularity)
516544

517545
return list(
518-
ManualReviewEntity.objects.filter(
546+
HITLQueue._base_manager.filter(
519547
organization_id=organization_id,
520-
status="APPROVED",
521-
modified_at__gte=start_date,
522-
modified_at__lte=end_date,
548+
state=HITLQueue.State.APPROVED,
549+
approved_at__gte=start_date,
550+
approved_at__lte=end_date,
523551
)
524-
.annotate(period=trunc_func("modified_at"))
552+
.annotate(period=trunc_func("approved_at"))
525553
.values("period")
526554
.annotate(value=Count("id"))
527555
.order_by("period")
@@ -595,6 +623,14 @@ def get_all_metrics_summary(
595623
r["value"] or 0
596624
for r in cls.get_failed_pages(organization_id, start_date, end_date)
597625
),
626+
"hitl_reviews": sum(
627+
r["value"]
628+
for r in cls.get_hitl_reviews(organization_id, start_date, end_date)
629+
),
630+
"hitl_completions": sum(
631+
r["value"]
632+
for r in cls.get_hitl_completions(organization_id, start_date, end_date)
633+
),
598634
}
599635

600636
@staticmethod
@@ -658,6 +694,7 @@ def get_recent_activity(
658694
results.append(
659695
{
660696
"id": str(execution.id),
697+
"execution_id": str(execution.workflow_execution.id),
661698
"type": exec_type,
662699
"file_name": execution.file_name,
663700
"status": execution.status,

backend/dashboard_metrics/views.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,8 @@ def live_series(self, request: Request) -> Response:
774774
"etl_pipeline_executions": MetricsQueryService.get_etl_pipeline_executions,
775775
"llm_usage": MetricsQueryService.get_llm_usage_cost,
776776
"prompt_executions": MetricsQueryService.get_prompt_executions,
777+
"hitl_reviews": MetricsQueryService.get_hitl_reviews,
778+
"hitl_completions": MetricsQueryService.get_hitl_completions,
777779
}
778780

779781
# Filter by specific metric if requested

frontend/src/components/logging/detailed-logs/DetailedLogs.jsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
} from "antd";
2525
import PropTypes from "prop-types";
2626
import { useEffect, useRef, useState } from "react";
27-
import { useNavigate, useParams } from "react-router-dom";
27+
import { useLocation, useNavigate, useParams } from "react-router-dom";
2828

2929
import { useAxiosPrivate } from "../../../hooks/useAxiosPrivate";
3030
import { useExceptionHandler } from "../../../hooks/useExceptionHandler";
@@ -95,8 +95,10 @@ const DetailedLogs = () => {
9595
const { setAlertDetails } = useAlertStore();
9696
const handleException = useExceptionHandler();
9797
const navigate = useNavigate();
98+
const location = useLocation();
9899
const { getUrl } = useRequestUrl();
99100
const copyToClipboard = useCopyToClipboard();
101+
const cameFromMetrics = location.state?.from === "metrics";
100102

101103
const [executionDetails, setExecutionDetails] = useState();
102104
const [executionFiles, setExecutionFiles] = useState();
@@ -462,7 +464,13 @@ const DetailedLogs = () => {
462464
type="text"
463465
shape="circle"
464466
icon={<ArrowLeftOutlined />}
465-
onClick={() => navigate(`/${sessionDetails?.orgName}/logs`)}
467+
onClick={() =>
468+
navigate(
469+
cameFromMetrics
470+
? `/${sessionDetails?.orgName}/metrics`
471+
: `/${sessionDetails?.orgName}/logs`,
472+
)
473+
}
466474
/>
467475
{type} Execution ID {id}
468476
<Button

frontend/src/components/metrics-dashboard/MetricsDashboard.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,15 @@
232232
border-bottom: 1px solid #f0f0f0;
233233
}
234234

235+
.recent-activity-clickable {
236+
cursor: pointer;
237+
transition: background-color 0.2s;
238+
}
239+
240+
.recent-activity-clickable:hover {
241+
background-color: #f5f5f5;
242+
}
243+
235244
.recent-activity-item:last-child {
236245
border-bottom: none;
237246
}

frontend/src/components/metrics-dashboard/RecentActivity.jsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { Card, Empty, List, Spin, Tag, Tooltip, Typography } from "antd";
1212
import dayjs from "dayjs";
1313
import relativeTime from "dayjs/plugin/relativeTime";
1414
import PropTypes from "prop-types";
15+
import { useNavigate } from "react-router-dom";
16+
import { useSessionStore } from "../../store/session-store";
1517

1618
import "./MetricsDashboard.css";
1719

@@ -49,16 +51,19 @@ const TYPE_CONFIG = {
4951
label: "ETL Pipeline",
5052
icon: <BranchesOutlined />,
5153
color: "#722ed1",
54+
logType: "ETL",
5255
},
5356
api: {
5457
label: "API Request",
5558
icon: <ApiOutlined />,
5659
color: "#1890ff",
60+
logType: "API",
5761
},
5862
workflow: {
5963
label: "Workflow",
6064
icon: <PlayCircleOutlined />,
6165
color: "#52c41a",
66+
logType: "WF",
6267
},
6368
};
6469

@@ -69,6 +74,18 @@ const TYPE_CONFIG = {
6974
* @return {JSX.Element} The rendered recent activity component.
7075
*/
7176
function RecentActivity({ data, loading }) {
77+
const navigate = useNavigate();
78+
const { sessionDetails } = useSessionStore();
79+
const orgName = sessionDetails?.orgName;
80+
81+
const handleActivityClick = (item) => {
82+
if (!item.execution_id || !orgName) return;
83+
const typeConfig = TYPE_CONFIG[item.type] || TYPE_CONFIG.workflow;
84+
navigate(`/${orgName}/logs/${typeConfig.logType}/${item.execution_id}`, {
85+
state: { from: "metrics" },
86+
});
87+
};
88+
7289
if (loading) {
7390
return (
7491
<Card title="Recent Activity" className="metrics-chart-card">
@@ -100,7 +117,10 @@ function RecentActivity({ data, loading }) {
100117
const typeConfig = TYPE_CONFIG[item.type] || TYPE_CONFIG.workflow;
101118

102119
return (
103-
<List.Item className="recent-activity-item">
120+
<List.Item
121+
className="recent-activity-item recent-activity-clickable"
122+
onClick={() => handleActivityClick(item)}
123+
>
104124
<div className="recent-activity-content">
105125
<div className="recent-activity-header">
106126
<span
@@ -142,6 +162,7 @@ RecentActivity.propTypes = {
142162
activity: PropTypes.arrayOf(
143163
PropTypes.shape({
144164
id: PropTypes.string.isRequired,
165+
execution_id: PropTypes.string,
145166
type: PropTypes.oneOf(["etl", "api", "workflow"]).isRequired,
146167
file_name: PropTypes.string.isRequired,
147168
status: PropTypes.string.isRequired,

frontend/src/components/navigations/side-nav-bar/SideNavBar.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,11 @@
174174
.space-styles:hover .sidebar-antd-icon {
175175
color: white;
176176
}
177+
178+
.sidebar-menu-tag {
179+
margin-left: 6px;
180+
font-size: 10px;
181+
line-height: 16px;
182+
padding: 0 4px;
183+
border-radius: 4px;
184+
}

frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
Layout,
1111
Popover,
1212
Space,
13+
Tag,
1314
Tooltip,
1415
Typography,
1516
} from "antd";
@@ -19,6 +20,7 @@ import { useNavigate } from "react-router-dom";
1920
import apiDeploy from "../../../assets/api-deployments.svg";
2021
import ConnectorsIcon from "../../../assets/connectors.svg";
2122
import CustomTools from "../../../assets/custom-tools-icon.svg";
23+
import DashboardIcon from "../../../assets/dashboard.svg";
2224
import EmbeddingIcon from "../../../assets/embedding.svg";
2325
import etl from "../../../assets/etl.svg";
2426
import LlmIcon from "../../../assets/llm.svg";
@@ -454,6 +456,17 @@ const SideNavBar = ({ collapsed, setCollapsed }) => {
454456
unstractMenuItems[1].subMenu.unshift(dashboardSideMenuItem(orgName));
455457
}
456458

459+
// Add metrics dashboard menu item (available for both OSS and cloud)
460+
unstractMenuItems[1].subMenu.unshift({
461+
id: 2.0,
462+
title: "Dashboard",
463+
tag: "New",
464+
description: "View platform usage metrics and analytics",
465+
image: DashboardIcon,
466+
path: `/${orgName}/metrics`,
467+
active: globalThis.location.pathname.startsWith(`/${orgName}/metrics`),
468+
});
469+
457470
// If selectedProduct is verticals and menu is null, don't show any sidebar items
458471
const data =
459472
selectedProduct === "verticals" && menu === null
@@ -702,6 +715,14 @@ const SideNavBar = ({ collapsed, setCollapsed }) => {
702715
<div>
703716
<Typography className="sidebar-item-text fs-14">
704717
{el.title}
718+
{el.tag && (
719+
<Tag
720+
color="blue"
721+
className="sidebar-menu-tag"
722+
>
723+
{el.tag}
724+
</Tag>
725+
)}
705726
</Typography>
706727
<Typography className="sidebar-item-text fs-11">
707728
{el.description}

0 commit comments

Comments
 (0)