Skip to content
Merged
Show file tree
Hide file tree
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
62 changes: 49 additions & 13 deletions backend/dashboard_metrics/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from typing import Any

from account_usage.models import PageUsage
from account_v2.models import Organization
from api_v2.models import APIDeployment
from django.db.models import CharField, Count, OuterRef, Q, Subquery, Sum
from django.db.models.functions import Cast, Coalesce, TruncDay, TruncHour, TruncWeek
Expand All @@ -25,6 +26,19 @@
from unstract.core.data_models import ExecutionStatus


def _get_hitl_queue_model():
"""Get HITLQueue model if available (cloud-only).

Returns None on OSS where manual_review_v2 is not installed.
"""
try:
from pluggable_apps.manual_review_v2.models import HITLQueue

return HITLQueue
except ImportError:
return None


def _get_usage_queryset():
"""Get Usage queryset bypassing the organization context filter.

Expand Down Expand Up @@ -110,6 +124,10 @@ def get_pages_processed(

Sums pages_processed field grouped by time period.

Note: PageUsage.organization_id stores the Organization's string
identifier (organization.organization_id), NOT the UUID PK.
We look up the string identifier from the UUID passed by callers.

Args:
organization_id: Organization UUID string
start_date: Start of date range
Expand All @@ -119,11 +137,17 @@ def get_pages_processed(
Returns:
List of dicts with 'period' and 'value' keys
"""
try:
org = Organization.objects.get(id=organization_id)
org_identifier = org.organization_id
except Organization.DoesNotExist:
return []

trunc_func = MetricsQueryService._get_trunc_func(granularity)

return list(
PageUsage.objects.filter(
organization_id=organization_id,
organization_id=org_identifier,
created_at__gte=start_date,
created_at__lte=end_date,
)
Expand Down Expand Up @@ -458,6 +482,9 @@ def get_hitl_reviews(
) -> list[dict[str, Any]]:
"""Query HITL review counts from manual_review_v2.

Counts all HITLQueue records created in the date range,
regardless of their current state.

Returns empty list on OSS where manual_review_v2 is not installed.

Args:
Expand All @@ -469,15 +496,14 @@ def get_hitl_reviews(
Returns:
List of dicts with 'period' and 'value' keys
"""
try:
from manual_review_v2.models import ManualReviewEntity
except ImportError:
HITLQueue = _get_hitl_queue_model()
if HITLQueue is None:
return []

trunc_func = MetricsQueryService._get_trunc_func(granularity)

return list(
ManualReviewEntity.objects.filter(
HITLQueue._base_manager.filter(
organization_id=organization_id,
created_at__gte=start_date,
created_at__lte=end_date,
Expand All @@ -497,6 +523,9 @@ def get_hitl_completions(
) -> list[dict[str, Any]]:
"""Query completed HITL reviews from manual_review_v2.

Counts HITLQueue records with state='approved' that were approved
within the date range (using approved_at timestamp).

Returns empty list on OSS where manual_review_v2 is not installed.

Args:
Expand All @@ -508,21 +537,20 @@ def get_hitl_completions(
Returns:
List of dicts with 'period' and 'value' keys
"""
try:
from manual_review_v2.models import ManualReviewEntity
except ImportError:
HITLQueue = _get_hitl_queue_model()
if HITLQueue is None:
return []

trunc_func = MetricsQueryService._get_trunc_func(granularity)

return list(
ManualReviewEntity.objects.filter(
HITLQueue._base_manager.filter(
organization_id=organization_id,
status="APPROVED",
modified_at__gte=start_date,
modified_at__lte=end_date,
state=HITLQueue.State.APPROVED,
approved_at__gte=start_date,
approved_at__lte=end_date,
)
.annotate(period=trunc_func("modified_at"))
.annotate(period=trunc_func("approved_at"))
.values("period")
.annotate(value=Count("id"))
.order_by("period")
Expand Down Expand Up @@ -596,6 +624,14 @@ def get_all_metrics_summary(
r["value"] or 0
for r in cls.get_failed_pages(organization_id, start_date, end_date)
),
"hitl_reviews": sum(
r["value"]
for r in cls.get_hitl_reviews(organization_id, start_date, end_date)
),
"hitl_completions": sum(
r["value"]
for r in cls.get_hitl_completions(organization_id, start_date, end_date)
),
}

@staticmethod
Expand Down
2 changes: 2 additions & 0 deletions backend/dashboard_metrics/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,8 @@ def live_series(self, request: Request) -> Response:
"etl_pipeline_executions": MetricsQueryService.get_etl_pipeline_executions,
"llm_usage": MetricsQueryService.get_llm_usage_cost,
"prompt_executions": MetricsQueryService.get_prompt_executions,
"hitl_reviews": MetricsQueryService.get_hitl_reviews,
"hitl_completions": MetricsQueryService.get_hitl_completions,
}

# Filter by specific metric if requested
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,11 @@
.space-styles:hover .sidebar-antd-icon {
color: white;
}

.sidebar-menu-tag {
margin-left: 6px;
font-size: 10px;
line-height: 16px;
padding: 0 4px;
border-radius: 4px;
}
21 changes: 21 additions & 0 deletions frontend/src/components/navigations/side-nav-bar/SideNavBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Layout,
Popover,
Space,
Tag,
Tooltip,
Typography,
} from "antd";
Expand All @@ -19,6 +20,7 @@ import { useNavigate } from "react-router-dom";
import apiDeploy from "../../../assets/api-deployments.svg";
import ConnectorsIcon from "../../../assets/connectors.svg";
import CustomTools from "../../../assets/custom-tools-icon.svg";
import DashboardIcon from "../../../assets/dashboard.svg";
import EmbeddingIcon from "../../../assets/embedding.svg";
import etl from "../../../assets/etl.svg";
import LlmIcon from "../../../assets/llm.svg";
Expand Down Expand Up @@ -454,6 +456,17 @@ const SideNavBar = ({ collapsed, setCollapsed }) => {
unstractMenuItems[1].subMenu.unshift(dashboardSideMenuItem(orgName));
}

// Add metrics dashboard menu item (available for both OSS and cloud)
unstractMenuItems[1].subMenu.unshift({
id: 2.0,
title: "Dashboard",
tag: "New",
description: "View platform usage metrics and analytics",
image: DashboardIcon,
path: `/${orgName}/metrics`,
active: globalThis.location.pathname.startsWith(`/${orgName}/metrics`),
});

// If selectedProduct is verticals and menu is null, don't show any sidebar items
const data =
selectedProduct === "verticals" && menu === null
Expand Down Expand Up @@ -702,6 +715,14 @@ const SideNavBar = ({ collapsed, setCollapsed }) => {
<div>
<Typography className="sidebar-item-text fs-14">
{el.title}
{el.tag && (
<Tag
color="blue"
className="sidebar-menu-tag"
>
{el.tag}
</Tag>
)}
</Typography>
<Typography className="sidebar-item-text fs-11">
{el.description}
Expand Down
Loading