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
14 changes: 12 additions & 2 deletions daiv/accounts/templates/accounts/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@ <h2 class="text-[14px] font-semibold tracking-tight text-gray-300">Agent Activit
{% for counter in counters %}
<div class="flex items-baseline justify-between py-3 first:pt-0 last:pb-0">
<span class="text-[13px] text-gray-500">{{ counter.label }}</span>
<span class="text-[20px] font-bold tabular-nums leading-none {{ counter.value|success_rate_color }}">{{ counter.value }}</span>
<div class="text-right">
<span class="text-[20px] font-bold tabular-nums leading-none {{ counter.value|success_rate_color }}">{{ counter.value }}</span>
{% if counter.today %}
<div class="mt-1 text-[11px] font-medium tabular-nums text-emerald-400/80">+{{ counter.today }} today</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
Expand Down Expand Up @@ -89,7 +94,12 @@ <h2 class="text-[14px] font-semibold tracking-tight text-gray-300">Code Velocity
</span>
{% endif %}
</span>
<span class="text-[20px] font-bold tabular-nums leading-none {% if not counter.plain %}{{ counter.value|success_rate_color }}{% endif %}">{{ counter.value }}</span>
<div class="text-right">
<span class="text-[20px] font-bold tabular-nums leading-none {% if not counter.plain %}{{ counter.value|success_rate_color }}{% endif %}">{{ counter.value }}</span>
{% if counter.today %}
<div class="mt-1 text-[11px] font-medium tabular-nums text-emerald-400/80">+{{ counter.today }} today</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
Expand Down
71 changes: 44 additions & 27 deletions daiv/accounts/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
from datetime import timedelta
from datetime import date, timedelta

from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
Expand Down Expand Up @@ -35,6 +35,11 @@ def homepage(request):
DEFAULT_PERIOD = "30d"


def _format_pct(numerator: int, denominator: int) -> str:
"""Format a ratio as a rounded percentage string, or a dash when the denominator is zero."""
return f"{round(numerator / denominator * 100)}%" if denominator else "—"


class DashboardView(LoginRequiredMixin, TemplateView):
template_name = "accounts/dashboard.html"

Expand All @@ -45,87 +50,99 @@ def get_context_data(self, **kwargs):
if period not in PERIOD_DAYS:
period = DEFAULT_PERIOD
days = PERIOD_DAYS[period]
cutoff_date = localdate() - timedelta(days=days) if days is not None else None
today = localdate()
cutoff_date = today - timedelta(days=days) if days is not None else None

context["counters"] = self._get_activity_counters(cutoff_date, today)
context["active_api_keys"] = APIKey.objects.filter(user=self.request.user, revoked=False).count()
context["periods"] = [{"key": key, "label": label} for key, label, _ in PERIOD_CHOICES]
context["current_period"] = period
context["merge_counters"] = self._get_merge_counters(cutoff_date, today)

return context

def _get_activity_counters(self, cutoff_date: date | None, today: date) -> list[dict]:
tasks = DBTaskResult.objects.filter(task_path__in=TASK_PATHS)
if cutoff_date is not None:
tasks = tasks.filter(enqueued_at__date__gte=cutoff_date)

successful = Q(status=TaskResultStatus.SUCCESSFUL)
code_changes = Q(return_value__code_changes=True)
today_q = Q(enqueued_at__date=today)
stats = tasks.aggregate(
total=Count("id"),
successful=Count("id", filter=successful),
issues=Count("id", filter=successful & code_changes & Q(task_path=ISSUE_TASK_PATH)),
mrs=Count("id", filter=successful & code_changes & Q(task_path=MR_TASK_PATH)),
today_total=Count("id", filter=today_q),
today_issues=Count("id", filter=today_q & successful & code_changes & Q(task_path=ISSUE_TASK_PATH)),
today_mrs=Count("id", filter=today_q & successful & code_changes & Q(task_path=MR_TASK_PATH)),
)
active_api_keys = APIKey.objects.filter(user=self.request.user, revoked=False).count()

total = stats["total"]
context["counters"] = [
{"label": "Jobs processed", "value": total},
{"label": "Success rate", "value": f"{round(stats['successful'] / total * 100)}%" if total else "—"},
{"label": "Issues resolved", "value": stats["issues"]},
{"label": "MR reviews addressed", "value": stats["mrs"]},
return [
{"label": "Jobs processed", "value": total, "today": stats["today_total"]},
{"label": "Success rate", "value": _format_pct(stats["successful"], total)},
{"label": "Issues resolved", "value": stats["issues"], "today": stats["today_issues"]},
{"label": "MR reviews addressed", "value": stats["mrs"], "today": stats["today_mrs"]},
]
context["active_api_keys"] = active_api_keys
context["periods"] = [{"key": key, "label": label} for key, label, _ in PERIOD_CHOICES]
context["current_period"] = period

# Merge metrics
def _get_merge_counters(self, cutoff_date: date | None, today: date) -> list[dict]:
merges = MergeMetric.objects.all()
if cutoff_date is not None:
merges = merges.filter(merged_at__date__gte=cutoff_date)

merge_stats = merges.aggregate(
today_q = Q(merged_at__date=today)
stats = merges.aggregate(
total=Count("id"),
total_added=Sum("lines_added", default=0),
total_removed=Sum("lines_removed", default=0),
daiv_added=Sum("daiv_lines_added", default=0),
daiv_removed=Sum("daiv_lines_removed", default=0),
total_commits_sum=Sum("total_commits", default=0),
daiv_commits_sum=Sum("daiv_commits", default=0),
today_total=Count("id", filter=today_q),
today_added=Sum("lines_added", default=0, filter=today_q),
today_removed=Sum("lines_removed", default=0, filter=today_q),
)

merge_total = merge_stats["total"]
daiv_lines = merge_stats["daiv_added"] + merge_stats["daiv_removed"]
total_lines = merge_stats["total_added"] + merge_stats["total_removed"]
daiv_line_pct = f"{round(daiv_lines / total_lines * 100)}%" if total_lines else "—"
total_commits = merge_stats["total_commits_sum"]
daiv_commit_pct = f"{round(merge_stats['daiv_commits_sum'] / total_commits * 100)}%" if total_commits else "—"
total_lines = stats["total_added"] + stats["total_removed"]
daiv_lines = stats["daiv_added"] + stats["daiv_removed"]
total_commits = stats["total_commits_sum"]

context["merge_counters"] = [
return [
{
"label": "Total merges",
"value": merge_total,
"value": stats["total"],
"tooltip": "Number of MRs/PRs merged into default branches.",
"today": stats["today_total"],
},
{
"label": "Lines added",
"value": merge_stats["total_added"],
"value": stats["total_added"],
"tooltip": "Total lines added across all merged MRs/PRs.",
"today": stats["today_added"],
},
{
"label": "Lines removed",
"value": merge_stats["total_removed"],
"value": stats["total_removed"],
"tooltip": "Total lines removed across all merged MRs/PRs.",
"today": stats["today_removed"],
},
{
"label": "DAIV contribution",
"value": daiv_line_pct,
"value": _format_pct(daiv_lines, total_lines),
"tooltip": "Percentage of total lines (added + removed) authored by DAIV, based on commit authorship.",
"plain": True,
},
{
"label": "DAIV commit share",
"value": daiv_commit_pct,
"value": _format_pct(stats["daiv_commits_sum"], total_commits),
"tooltip": "Percentage of total commits authored by DAIV across all merged MRs/PRs.",
"plain": True,
},
]

return context


class APIKeyListView(LoginRequiredMixin, TemplateView):
template_name = "accounts/api_keys.html"
Expand Down
Loading