From e7bc3b4b4c65f2df3f4a92057151c83783a11d9f Mon Sep 17 00:00:00 2001
From: Kaustubh Maske Patil <37668193+nikochiko@users.noreply.github.com>
Date: Fri, 6 Dec 2024 12:12:07 +0530
Subject: [PATCH] add ux for analytics tab & queries for calculating credit
usage / run count
---
daras_ai_v2/icons.py | 1 +
routers/account.py | 64 ++++++++++++++++++++++++++++++++++++++++++++
workspaces/models.py | 45 +++++++++++++++++++++++++++++++
3 files changed, 110 insertions(+)
diff --git a/daras_ai_v2/icons.py b/daras_ai_v2/icons.py
index 30e90f77e..821f999fe 100644
--- a/daras_ai_v2/icons.py
+++ b/daras_ai_v2/icons.py
@@ -35,6 +35,7 @@
member = ''
chevron_right = ''
check = ''
+analytics = ''
# brands
github = ''
diff --git a/routers/account.py b/routers/account.py
index 22b4376ad..b725e4353 100644
--- a/routers/account.py
+++ b/routers/account.py
@@ -109,6 +109,12 @@ def account_route(request: Request):
)
+@gui.route(app, "/account/analytics")
+def analytics_route(request: Request):
+ with account_page_wrapper(request, AccountTabs.analytics) as current_workspace:
+ analytics_tab(request)
+
+
@gui.route(app, "/account/profile/")
def profile_route(request: Request):
with account_page_wrapper(request, AccountTabs.profile) as current_workspace:
@@ -227,6 +233,7 @@ class AccountTabs(TabData, Enum):
saved = TabData(title=f"{icons.save} Saved", route=saved_route)
api_keys = TabData(title=f"{icons.api} API Keys", route=api_keys_route)
billing = TabData(title=f"{icons.billing} Billing", route=account_route)
+ analytics = TabData(title=f"{icons.analytics} Analytics", route=analytics_route)
@property
def url_path(self) -> str:
@@ -241,10 +248,12 @@ def get_tabs_for_user(
if workspace.is_personal:
ret.remove(cls.members)
+ ret.remove(cls.analytics)
else:
ret.remove(cls.profile)
if not workspace.memberships.get(user=user).can_edit_workspace():
ret.remove(cls.billing)
+ ret.remove(cls.analytics)
return ret
@@ -345,6 +354,61 @@ def _render_run(pr: PublishedRun):
paginate_button(url=request.url, cursor=cursor)
+def analytics_tab(request: Request):
+ workspace = get_current_workspace(request.user, request.session)
+ gui.write("# Usage & Limits")
+ gui.caption(
+ f"Member, API & Integration usage for **{workspace.display_name(request.user)}**."
+ )
+
+ with gui.div(className="table-responsive"), gui.tag("table", className="table"):
+ with gui.tag("thead"), gui.tag("tr"):
+ with gui.tag("th", scope="col"):
+ gui.html("Name")
+ with gui.tag("th", scope="col"):
+ gui.html("Type")
+ with gui.tag("th", scope="col"):
+ gui.html("Runs")
+ with gui.tag("th", scope="col"):
+ gui.html("Credits Used")
+ with gui.tag("th", scope="col"):
+ gui.html("")
+
+ with gui.tag("tbody"):
+ for m in workspace.memberships.all():
+ with gui.tag("tr", className="no-margin"):
+ with gui.tag("td"):
+ gui.write(m.user.full_name())
+ with gui.tag("td"):
+ gui.html("User")
+ with gui.tag("td"):
+ gui.html(f"{m.get_run_count()}")
+ with gui.tag("td"):
+ gui.html(f"{m.get_credit_usage()} Cr")
+ with gui.tag("td"):
+ gui.html("")
+
+ with gui.tag("tr", className="no-margin"):
+ with gui.tag("td"):
+ gui.write(f"[API Keys]({get_route_path(api_keys_route)})")
+ with gui.tag("td"):
+ gui.html("API Key")
+ with gui.tag("td"):
+ gui.html(f"{workspace.get_api_key_run_count()}")
+ with gui.tag("td"):
+ gui.html(f"{workspace.get_api_key_credit_usage()} Cr")
+
+ with gui.tag("tr", className="no-margin"):
+ with gui.tag("td"):
+ gui.write("Bot Integrations")
+ with gui.tag("td"):
+ gui.html("Bot")
+ with gui.tag("td"):
+ gui.html(f"{workspace.get_bot_run_count()}")
+ with gui.tag("td"):
+ gui.html(f"{workspace.get_bot_credit_usage()} Cr")
+
+
def api_keys_tab(request: Request):
gui.write("# 🔐 API Keys")
workspace = get_current_workspace(request.user, request.session)
diff --git a/workspaces/models.py b/workspaces/models.py
index e4fb17331..3e621f485 100644
--- a/workspaces/models.py
+++ b/workspaces/models.py
@@ -12,6 +12,7 @@
from django.db import models, transaction, IntegrityError
from django.db.backends.base.schema import logger
from django.db.models.aggregates import Sum
+from django.db.models.functions import Abs
from django.db.models.query_utils import Q
from django.utils import timezone
from django.utils.text import slugify
@@ -256,6 +257,35 @@ def add_balance(
pass
raise
+ def get_api_key_run_count(self) -> int:
+ return (
+ self.saved_runs.filter(is_api_call=True, price__gt=0)
+ .exclude(messages__isnull=False)
+ .count()
+ )
+
+ def get_api_key_credit_usage(self) -> int:
+ return (
+ self.saved_runs.filter(is_api_call=True)
+ .exclude(messages__isnull=False) # exclude bot-integration runs
+ .aggregate(total=Sum("price"))
+ .get("total")
+ or 0
+ )
+
+ def get_bot_run_count(self) -> int:
+ return self.saved_runs.filter(
+ is_api_call=True, price__gt=0, messages__isnull=False
+ ).count()
+
+ def get_bot_credit_usage(self) -> int:
+ return (
+ self.saved_runs.filter(is_api_call=True, messages__isnull=False)
+ .aggregate(total=Sum("price"))
+ .get("total")
+ or 0
+ )
+
def get_or_create_stripe_customer(self) -> stripe.Customer:
customer = None
@@ -439,6 +469,21 @@ def can_invite(self):
and not self.workspace.is_personal
)
+ def get_run_count(self) -> int:
+ return self.workspace.saved_runs.filter(
+ uid=self.user.uid,
+ price__gt=0, # proxy for successful runs
+ is_api_call=False,
+ ).count()
+
+ def get_credit_usage(self) -> int:
+ return (
+ self.workspace.saved_runs.filter(
+ uid=self.user.uid, price__gt=0, is_api_call=False
+ ).aggregate(total=Sum("price"))["total"]
+ or 0
+ )
+
class WorkspaceInviteQuerySet(models.QuerySet):
def create_and_send_invite(