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(