diff --git a/graphs/tests/test_badge_handler.py b/graphs/tests/test_badge_handler.py
index e79f7d1214..c8b47ccf28 100644
--- a/graphs/tests/test_badge_handler.py
+++ b/graphs/tests/test_badge_handler.py
@@ -143,7 +143,7 @@ def test_invalid_extension(self):
response.data["detail"] == "File extension should be one of [ svg || txt ]"
)
- def test_unknown_bagde_incorrect_service(self):
+ def test_unknown_badge_incorrect_service(self):
response = self._get(
kwargs={
"service": "gih",
@@ -182,7 +182,7 @@ def test_unknown_bagde_incorrect_service(self):
assert expected_badge == badge
assert response.status_code == status.HTTP_200_OK
- def test_unknown_bagde_incorrect_owner(self):
+ def test_unknown_badge_incorrect_owner(self):
response = self._get(
kwargs={
"service": "gh",
@@ -221,7 +221,7 @@ def test_unknown_bagde_incorrect_owner(self):
assert expected_badge == badge
assert response.status_code == status.HTTP_200_OK
- def test_unknown_bagde_incorrect_repo(self):
+ def test_unknown_badge_incorrect_repo(self):
gh_owner = OwnerFactory(service="github")
response = self._get(
kwargs={
@@ -261,7 +261,7 @@ def test_unknown_bagde_incorrect_repo(self):
assert expected_badge == badge
assert response.status_code == status.HTTP_200_OK
- def test_unknown_bagde_no_branch(self):
+ def test_unknown_badge_no_branch(self):
gh_owner = OwnerFactory(service="github")
RepositoryFactory(author=gh_owner, active=True, private=False, name="repo1")
response = self._get(
@@ -302,7 +302,7 @@ def test_unknown_bagde_no_branch(self):
assert expected_badge == badge
assert response.status_code == status.HTTP_200_OK
- def test_unknown_bagde_no_commit(self):
+ def test_unknown_badge_no_commit(self):
gh_owner = OwnerFactory(service="github")
repo = RepositoryFactory(
author=gh_owner, active=True, private=False, name="repo1"
@@ -346,7 +346,7 @@ def test_unknown_bagde_no_commit(self):
assert expected_badge == badge
assert response.status_code == status.HTTP_200_OK
- def test_unknown_bagde_no_totals(self):
+ def test_unknown_badge_no_totals(self):
gh_owner = OwnerFactory(service="github")
repo = RepositoryFactory(
author=gh_owner, active=True, private=False, name="repo1"
diff --git a/graphs/tests/test_bundle_badge_handler.py b/graphs/tests/test_bundle_badge_handler.py
new file mode 100644
index 0000000000..0317ab05b2
--- /dev/null
+++ b/graphs/tests/test_bundle_badge_handler.py
@@ -0,0 +1,540 @@
+from unittest.mock import patch
+
+from rest_framework import status
+from rest_framework.test import APITestCase
+from shared.bundle_analysis import BundleAnalysisReport, BundleReport
+from shared.django_apps.core.tests.factories import (
+ BranchFactory,
+ CommitFactory,
+ OwnerFactory,
+ RepositoryFactory,
+)
+
+
+class MockBundleReport(BundleReport):
+ def __init__(self):
+ return
+
+ def total_size(self):
+ return 1234567
+
+
+class MockBundleAnalysisReport(BundleAnalysisReport):
+ def bundle_report(self, bundle_name: str):
+ if bundle_name == "idk":
+ return None
+ return MockBundleReport()
+
+
+class TestBundleBadgeHandler(APITestCase):
+ def _get(self, kwargs={}, data={}):
+ path = f"/{kwargs.get('service')}/{kwargs.get('owner_username')}/{kwargs.get('repo_name')}/graphs/bundle/{kwargs.get('bundle')}/badge.{kwargs.get('ext')}"
+ return self.client.get(path, data=data)
+
+ def _get_branch(self, kwargs={}, data={}):
+ path = f"/{kwargs.get('service')}/{kwargs.get('owner_username')}/{kwargs.get('repo_name')}/branch/{kwargs.get('branch')}/graphs/{kwargs.get('bundle')}/badge.{kwargs.get('ext')}"
+ return self.client.get(path, data=data)
+
+ def test_invalid_extension(self):
+ response = self._get(
+ kwargs={
+ "service": "gh",
+ "owner_username": "user",
+ "repo_name": "repo",
+ "ext": "png",
+ "bundle": "asdf",
+ }
+ )
+ assert response.status_code == status.HTTP_404_NOT_FOUND
+ assert (
+ response.data["detail"] == "File extension should be one of [ svg || txt ]"
+ )
+
+ def test_unknown_badge_incorrect_service(self):
+ response = self._get(
+ kwargs={
+ "service": "gih",
+ "owner_username": "user",
+ "repo_name": "repo",
+ "ext": "svg",
+ "bundle": "asdf",
+ }
+ )
+ expected_badge = """
+"""
+ badge = response.content.decode("utf-8")
+ badge = [line.strip() for line in badge.split("\n")]
+ expected_badge = [line.strip() for line in expected_badge.split("\n")]
+ assert expected_badge == badge
+ assert response.status_code == status.HTTP_200_OK
+
+ def test_unknown_badge_incorrect_owner(self):
+ response = self._get(
+ kwargs={
+ "service": "gh",
+ "owner_username": "user1233",
+ "repo_name": "repo",
+ "ext": "svg",
+ "bundle": "asdf",
+ }
+ )
+ expected_badge = """
+"""
+ badge = response.content.decode("utf-8")
+ badge = [line.strip() for line in badge.split("\n")]
+ expected_badge = [line.strip() for line in expected_badge.split("\n")]
+ assert expected_badge == badge
+ assert response.status_code == status.HTTP_200_OK
+
+ def test_unknown_badge_incorrect_repo(self):
+ gh_owner = OwnerFactory(service="github")
+ response = self._get(
+ kwargs={
+ "service": "gh",
+ "owner_username": gh_owner.username,
+ "repo_name": "repo",
+ "ext": "svg",
+ "bundle": "asdf",
+ }
+ )
+ expected_badge = """
+"""
+ badge = response.content.decode("utf-8")
+ badge = [line.strip() for line in badge.split("\n")]
+ expected_badge = [line.strip() for line in expected_badge.split("\n")]
+ assert expected_badge == badge
+ assert response.status_code == status.HTTP_200_OK
+
+ def test_unknown_badge_private_repo_wrong_token(self):
+ gh_owner = OwnerFactory(service="github")
+ RepositoryFactory(
+ author=gh_owner, active=True, private=True, name="repo1", image_token="asdf"
+ )
+ response = self._get(
+ kwargs={
+ "service": "gh",
+ "owner_username": gh_owner.username,
+ "repo_name": "repo1",
+ "ext": "svg",
+ "bundle": "asdf",
+ }
+ )
+ expected_badge = """
+"""
+ badge = response.content.decode("utf-8")
+ badge = [line.strip() for line in badge.split("\n")]
+ expected_badge = [line.strip() for line in expected_badge.split("\n")]
+ assert expected_badge == badge
+ assert response.status_code == status.HTTP_200_OK
+
+ def test_unknown_badge_no_branch(self):
+ gh_owner = OwnerFactory(service="github")
+ RepositoryFactory(author=gh_owner, active=True, private=False, name="repo1")
+ response = self._get(
+ kwargs={
+ "service": "gh",
+ "owner_username": gh_owner.username,
+ "repo_name": "repo1",
+ "ext": "svg",
+ "bundle": "asdf",
+ }
+ )
+ expected_badge = """
+"""
+ badge = response.content.decode("utf-8")
+ badge = [line.strip() for line in badge.split("\n")]
+ expected_badge = [line.strip() for line in expected_badge.split("\n")]
+ assert expected_badge == badge
+ assert response.status_code == status.HTTP_200_OK
+
+ def test_unknown_badge_no_commit(self):
+ gh_owner = OwnerFactory(service="github")
+ repo = RepositoryFactory(
+ author=gh_owner, active=True, private=False, name="repo1"
+ )
+ branch = BranchFactory(name="main", repository=repo)
+ repo.branch = branch
+ response = self._get(
+ kwargs={
+ "service": "gh",
+ "owner_username": gh_owner.username,
+ "repo_name": "repo1",
+ "ext": "svg",
+ "bundle": "asdf",
+ }
+ )
+ expected_badge = """
+"""
+
+ badge = response.content.decode("utf-8")
+ badge = [line.strip() for line in badge.split("\n")]
+ expected_badge = [line.strip() for line in expected_badge.split("\n")]
+ assert expected_badge == badge
+ assert response.status_code == status.HTTP_200_OK
+
+ @patch("graphs.views.load_report")
+ def test_unknown_badge_no_report(self, mock_load_report):
+ gh_owner = OwnerFactory(service="github")
+ repo = RepositoryFactory(
+ author=gh_owner, active=True, private=False, name="repo1"
+ )
+ branch = BranchFactory(name="main", repository=repo)
+ repo.branch = branch
+ commit = CommitFactory(
+ repository=repo, commitid=repo.branch.head, branch="main"
+ )
+ branch.head = commit.commitid
+
+ mock_load_report.return_value = None
+
+ response = self._get(
+ kwargs={
+ "service": "gh",
+ "owner_username": gh_owner.username,
+ "repo_name": "repo1",
+ "ext": "svg",
+ "bundle": "asdf",
+ }
+ )
+ expected_badge = """
+"""
+
+ badge = response.content.decode("utf-8")
+ badge = [line.strip() for line in badge.split("\n")]
+ expected_badge = [line.strip() for line in expected_badge.split("\n")]
+ assert expected_badge == badge
+ assert response.status_code == status.HTTP_200_OK
+
+ @patch("graphs.views.load_report")
+ def test_unknown_badge_no_bundle(self, mock_load_report):
+ gh_owner = OwnerFactory(service="github")
+ repo = RepositoryFactory(
+ author=gh_owner, active=True, private=False, name="repo1"
+ )
+ branch = BranchFactory(name="main", repository=repo)
+ repo.branch = branch
+ commit = CommitFactory(
+ repository=repo, commitid=repo.branch.head, branch="main"
+ )
+ branch.head = commit.commitid
+
+ mock_load_report.return_value = MockBundleAnalysisReport()
+
+ response = self._get(
+ kwargs={
+ "service": "gh",
+ "owner_username": gh_owner.username,
+ "repo_name": "repo1",
+ "ext": "svg",
+ "bundle": "idk",
+ }
+ )
+ expected_badge = """
+"""
+
+ badge = response.content.decode("utf-8")
+ badge = [line.strip() for line in badge.split("\n")]
+ expected_badge = [line.strip() for line in expected_badge.split("\n")]
+ assert expected_badge == badge
+ assert response.status_code == status.HTTP_200_OK
+
+ @patch("graphs.views.load_report")
+ def test_bundle_badge(self, mock_load_report):
+ gh_owner = OwnerFactory(service="github")
+ repo = RepositoryFactory(
+ author=gh_owner, active=True, private=False, name="repo1"
+ )
+ branch = BranchFactory(name="main", repository=repo)
+ repo.branch = branch
+ commit = CommitFactory(
+ repository=repo, commitid=repo.branch.head, branch="main"
+ )
+ branch.head = commit.commitid
+
+ mock_load_report.return_value = MockBundleAnalysisReport()
+
+ response = self._get(
+ kwargs={
+ "service": "gh",
+ "owner_username": gh_owner.username,
+ "repo_name": "repo1",
+ "ext": "svg",
+ "bundle": "asdf",
+ }
+ )
+ expected_badge = """
+"""
+
+ badge = response.content.decode("utf-8")
+ badge = [line.strip() for line in badge.split("\n")]
+ expected_badge = [line.strip() for line in expected_badge.split("\n")]
+ assert expected_badge == badge
+ assert response.status_code == status.HTTP_200_OK
+
+ @patch("graphs.views.load_report")
+ def test_bundle_badge_text(self, mock_load_report):
+ gh_owner = OwnerFactory(service="github")
+ repo = RepositoryFactory(
+ author=gh_owner, active=True, private=False, name="repo1"
+ )
+ branch = BranchFactory(name="main", repository=repo)
+ repo.branch = branch
+ commit = CommitFactory(
+ repository=repo, commitid=repo.branch.head, branch="main"
+ )
+ branch.head = commit.commitid
+
+ mock_load_report.return_value = MockBundleAnalysisReport()
+
+ response = self._get(
+ kwargs={
+ "service": "gh",
+ "owner_username": gh_owner.username,
+ "repo_name": "repo1",
+ "ext": "txt",
+ "bundle": "asdf",
+ }
+ )
+ expected_badge = "1.23MB"
+
+ badge = response.content.decode("utf-8")
+ badge = [line.strip() for line in badge.split("\n")]
+ expected_badge = [line.strip() for line in expected_badge.split("\n")]
+ assert expected_badge == badge
+ assert response.status_code == status.HTTP_200_OK
+
+ @patch("graphs.views.load_report")
+ def test_bundle_badge_unsupported_precision_defaults_to_2(self, mock_load_report):
+ gh_owner = OwnerFactory(service="github")
+ repo = RepositoryFactory(
+ author=gh_owner, active=True, private=False, name="repo1"
+ )
+ branch = BranchFactory(name="main", repository=repo)
+ repo.branch = branch
+ commit = CommitFactory(
+ repository=repo, commitid=repo.branch.head, branch="main"
+ )
+ branch.head = commit.commitid
+
+ mock_load_report.return_value = MockBundleAnalysisReport()
+
+ response = self._get(
+ kwargs={
+ "service": "gh",
+ "owner_username": gh_owner.username,
+ "repo_name": "repo1",
+ "ext": "txt",
+ "bundle": "asdf",
+ },
+ data={"precision": "asdf"},
+ )
+ expected_badge = "1.23MB"
+
+ badge = response.content.decode("utf-8")
+ badge = [line.strip() for line in badge.split("\n")]
+ expected_badge = [line.strip() for line in expected_badge.split("\n")]
+ assert expected_badge == badge
+
+ @patch("graphs.views.load_report")
+ def test_bundle_badge_private_repo_correct_token(self, mock_load_report):
+ gh_owner = OwnerFactory(service="github")
+ repo = RepositoryFactory(
+ author=gh_owner, active=True, private=True, name="repo1", image_token="asdf"
+ )
+ branch = BranchFactory(name="main", repository=repo)
+ repo.branch = branch
+ commit = CommitFactory(
+ repository=repo, commitid=repo.branch.head, branch="main"
+ )
+ branch.head = commit.commitid
+
+ mock_load_report.return_value = MockBundleAnalysisReport()
+
+ response = self._get(
+ kwargs={
+ "service": "gh",
+ "owner_username": gh_owner.username,
+ "repo_name": "repo1",
+ "ext": "txt",
+ "bundle": "asdf",
+ },
+ data={"token": "asdf"},
+ )
+ expected_badge = "1.23MB"
+
+ badge = response.content.decode("utf-8")
+ badge = [line.strip() for line in badge.split("\n")]
+ expected_badge = [line.strip() for line in expected_badge.split("\n")]
+ assert expected_badge == badge
+ assert response.status_code == status.HTTP_200_OK
diff --git a/graphs/urls.py b/graphs/urls.py
index bc0e0d45c8..cf069e9a42 100644
--- a/graphs/urls.py
+++ b/graphs/urls.py
@@ -1,6 +1,6 @@
from django.urls import re_path
-from .views import BadgeHandler, GraphHandler
+from .views import BadgeHandler, BundleBadgeHandler, GraphHandler
urlpatterns = [
re_path(
@@ -13,6 +13,16 @@
BadgeHandler.as_view(),
name="default-badge",
),
+ re_path(
+ "branch/(?P.+)/(graph|graphs)/bundle/(?P.+)/badge.(?P[^/]+)",
+ BundleBadgeHandler.as_view(),
+ name="branch-bundle-badge",
+ ),
+ re_path(
+ "(graph|graphs)/bundle/(?P.+)/badge.(?P[^/]+)",
+ BundleBadgeHandler.as_view(),
+ name="default-bundle-badge",
+ ),
re_path(
"pull/(?P[^/]+)/(graph|graphs)/(?Ptree|icicle|sunburst|commits).(?P[^/]+)",
GraphHandler.as_view(),
diff --git a/graphs/views.py b/graphs/views.py
index 9eb9c52714..68f0779aeb 100644
--- a/graphs/views.py
+++ b/graphs/views.py
@@ -14,9 +14,15 @@
from api.shared.mixins import RepoPropertyMixin
from core.models import Branch, Pull
from graphs.settings import settings
+from services.bundle_analysis import load_report
from services.components import commit_components
-from .helpers.badge import format_coverage_precision, get_badge
+from .helpers.badge import (
+ format_bundle_bytes,
+ format_coverage_precision,
+ get_badge,
+ get_bundle_badge,
+)
from .helpers.graphs import icicle, sunburst, tree
from .mixins import GraphBadgeAPIMixin
@@ -193,6 +199,83 @@ def component_coverage(self, component_identifier: str, commit: Commit):
return filtered_report.totals.coverage
+class BundleBadgeHandler(APIView, RepoPropertyMixin, GraphBadgeAPIMixin):
+ content_negotiation_class = IgnoreClientContentNegotiation
+
+ permission_classes = [AllowAny]
+
+ extensions = ["svg", "txt"]
+ precisions = ["0", "1", "2"]
+ filename = "bundle-badge"
+
+ def get_object(self, request, *args, **kwargs):
+ # Validate precision query param
+ precision = self.request.query_params.get("precision", "2")
+ precision = int(precision) if precision in self.precisions else 2
+
+ bundle_size_bytes = self.get_bundle_size()
+
+ if self.kwargs.get("ext") == "txt":
+ return (
+ "unknown"
+ if bundle_size_bytes is None
+ else format_bundle_bytes(bundle_size_bytes, precision)
+ )
+
+ return get_bundle_badge(bundle_size_bytes, precision)
+
+ def get_bundle_size(self) -> int | None:
+ try:
+ repo = self.repo
+ except Http404:
+ log.warning("Repo not found", extra=dict(repo=self.kwargs.get("repo_name")))
+ return None
+
+ if repo.private and repo.image_token != self.request.query_params.get("token"):
+ log.warning(
+ "Token provided does not match repo's image token",
+ extra=dict(repo=repo),
+ )
+ return None
+
+ branch_name = self.kwargs.get("branch") or repo.branch
+ branch = Branch.objects.filter(
+ name=branch_name, repository_id=repo.repoid
+ ).first()
+
+ if branch is None:
+ log.warning(
+ "Branch not found", extra=dict(branch_name=branch_name, repo=repo)
+ )
+ return None
+
+ commit: Commit = repo.commits.filter(commitid=branch.head).first()
+ if commit is None:
+ log.warning("Commit not found", extra=dict(commit=branch.head))
+ return None
+
+ commit_bundles = load_report(commit)
+
+ if commit_bundles is None:
+ log.warning(
+ "Bundle analysis report not found for commit",
+ extra=dict(commit=branch.head),
+ )
+ return None
+
+ bundle_name = str(self.kwargs.get("bundle"))
+ bundle = commit_bundles.bundle_report(bundle_name)
+
+ if bundle is None:
+ log.warning(
+ "Bundle with provided name not found for commit",
+ extra=dict(commit=branch.head),
+ )
+ return None
+
+ return bundle.total_size()
+
+
class GraphHandler(APIView, RepoPropertyMixin, GraphBadgeAPIMixin):
permission_classes = [AllowAny]