From 32e7838095a52f9d7175b5d7b25ae6cae6ec15b8 Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Tue, 19 May 2026 14:35:40 -0400 Subject: [PATCH 1/2] Init --- .../management/commands/release_tasks.py | 36 +++++++++---------- reports/constants.py | 8 +++++ 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/libraries/management/commands/release_tasks.py b/libraries/management/commands/release_tasks.py index b8f900941..e5fc65602 100644 --- a/libraries/management/commands/release_tasks.py +++ b/libraries/management/commands/release_tasks.py @@ -41,24 +41,24 @@ def __init__( def set_tasks(self): self.tasks = [ Action("Importing versions", self.import_versions), - Action( - "Importing most recent beta version", - ["import_beta_release", "--delete-versions"], - ), - Action("Importing development versions", ["import_development_versions"]), - Action("Importing libraries", ["update_libraries"]), - Action( - "Saving library-version relationships", self.import_library_versions - ), - Action("Adding library maintainers", ["update_maintainers"]), - Action("Adding library authors", ["update_authors"]), - Action( - "Adding library version authors", ["update_library_version_authors"] - ), - Action("Importing git commits", self.import_commits), - Action("Syncing mailinglist statistics", ["sync_mailinglist_stats"]), - Action("Updating github issues", ["update_issues"]), - Action("Updating slack activity buckets", ["fetch_slack_activity"]), + # Action( + # "Importing most recent beta version", + # ["import_beta_release", "--delete-versions"], + # ), + # Action("Importing development versions", ["import_development_versions"]), + # Action("Importing libraries", ["update_libraries"]), + # Action( + # "Saving library-version relationships", self.import_library_versions + # ), + # Action("Adding library maintainers", ["update_maintainers"]), + # Action("Adding library authors", ["update_authors"]), + # Action( + # "Adding library version authors", ["update_library_version_authors"] + # ), + # Action("Importing git commits", self.import_commits), + # Action("Syncing mailinglist statistics", ["sync_mailinglist_stats"]), + # Action("Updating github issues", ["update_issues"]), + # Action("Updating slack activity buckets", ["fetch_slack_activity"]), Action("Updating website statistics", self.update_website_statistics), Action("Importing mailing list counts", self.import_ml_counts), # Action("Generating report", self.generate_report), diff --git a/reports/constants.py b/reports/constants.py index 4549caf83..761cd8968 100644 --- a/reports/constants.py +++ b/reports/constants.py @@ -4,3 +4,11 @@ f"https://plausible.io/api/stats/{WEB_ANALYTICS_DOMAIN}/top-stats/?period=custom" "&from={:%Y-%m-%d}&to={:%Y-%m-%d}" ) +WEB_ANALYTICS_API_URL_V2 = "https://plausible.io/api/v2/query" +WEB_ANALYSTICS_API_TOP_STATS_PAYLOAD = { + "site_id": "dummy.site", + "metrics": ["visitors", "pageviews", "bounce_rate"], + "date_range": "7d", + "filters": [["is_not", "visit:country_name", [""]]], + "dimensions": ["visit:country_name", "visit:city_name"], +} From d7426d0a3dbe05a9f2d3f6e5a10de71a9877db71 Mon Sep 17 00:00:00 2001 From: Jeremy Childers Date: Tue, 19 May 2026 16:50:12 -0400 Subject: [PATCH 2/2] Update Plausible API call to V2 spec --- .../management/commands/release_tasks.py | 36 +++++++-------- reports/constants.py | 22 +++++++--- reports/models.py | 44 +++++++++++++++---- 3 files changed, 70 insertions(+), 32 deletions(-) diff --git a/libraries/management/commands/release_tasks.py b/libraries/management/commands/release_tasks.py index e5fc65602..b8f900941 100644 --- a/libraries/management/commands/release_tasks.py +++ b/libraries/management/commands/release_tasks.py @@ -41,24 +41,24 @@ def __init__( def set_tasks(self): self.tasks = [ Action("Importing versions", self.import_versions), - # Action( - # "Importing most recent beta version", - # ["import_beta_release", "--delete-versions"], - # ), - # Action("Importing development versions", ["import_development_versions"]), - # Action("Importing libraries", ["update_libraries"]), - # Action( - # "Saving library-version relationships", self.import_library_versions - # ), - # Action("Adding library maintainers", ["update_maintainers"]), - # Action("Adding library authors", ["update_authors"]), - # Action( - # "Adding library version authors", ["update_library_version_authors"] - # ), - # Action("Importing git commits", self.import_commits), - # Action("Syncing mailinglist statistics", ["sync_mailinglist_stats"]), - # Action("Updating github issues", ["update_issues"]), - # Action("Updating slack activity buckets", ["fetch_slack_activity"]), + Action( + "Importing most recent beta version", + ["import_beta_release", "--delete-versions"], + ), + Action("Importing development versions", ["import_development_versions"]), + Action("Importing libraries", ["update_libraries"]), + Action( + "Saving library-version relationships", self.import_library_versions + ), + Action("Adding library maintainers", ["update_maintainers"]), + Action("Adding library authors", ["update_authors"]), + Action( + "Adding library version authors", ["update_library_version_authors"] + ), + Action("Importing git commits", self.import_commits), + Action("Syncing mailinglist statistics", ["sync_mailinglist_stats"]), + Action("Updating github issues", ["update_issues"]), + Action("Updating slack activity buckets", ["fetch_slack_activity"]), Action("Updating website statistics", self.update_website_statistics), Action("Importing mailing list counts", self.import_ml_counts), # Action("Generating report", self.generate_report), diff --git a/reports/constants.py b/reports/constants.py index 761cd8968..06501eeeb 100644 --- a/reports/constants.py +++ b/reports/constants.py @@ -6,9 +6,21 @@ ) WEB_ANALYTICS_API_URL_V2 = "https://plausible.io/api/v2/query" WEB_ANALYSTICS_API_TOP_STATS_PAYLOAD = { - "site_id": "dummy.site", - "metrics": ["visitors", "pageviews", "bounce_rate"], - "date_range": "7d", - "filters": [["is_not", "visit:country_name", [""]]], - "dimensions": ["visit:country_name", "visit:city_name"], + "site_id": WEB_ANALYTICS_DOMAIN, + "metrics": [ + "visitors", + "pageviews", + "bounce_rate", + "visit_duration", + "views_per_visit", + "visits", + ], +} +WEB_ANALYTICS_CODENAME_MAPPING = { + "visitors": "Unique Visitors", + "pageviews": "Total Page pageviews", + "bounce_rate": "Bounce Rate", + "visit_duration": "Visit Duration", + "views_per_visit": "Views per visit", + "visits": "Total visits", } diff --git a/reports/models.py b/reports/models.py index af5f302e7..8470edca6 100644 --- a/reports/models.py +++ b/reports/models.py @@ -1,16 +1,24 @@ from datetime import timedelta import requests +import structlog from django.contrib.postgres.fields import DateRangeField +from django.conf import settings from django.db import models from django.db.backends.postgresql.psycopg_any import DateRange from django_extensions.db.models import TimeStampedModel -from reports.constants import WEB_ANALYTICS_API_URL +from reports.constants import ( + WEB_ANALYTICS_API_URL_V2, + WEB_ANALYSTICS_API_TOP_STATS_PAYLOAD, + WEB_ANALYTICS_CODENAME_MAPPING, +) from versions.models import Version INCLUSIVE = "[]" +logger = structlog.get_logger() + class WebsiteStatReport(TimeStampedModel): version = models.OneToOneField(Version, on_delete=models.CASCADE) @@ -36,16 +44,30 @@ def save(self, **kwargs): super().save(**kwargs) @property - def analytics_api_url(self) -> str: - return WEB_ANALYTICS_API_URL.format(self.period.lower, self.period.upper) + def analytics_api_payload(self): + base_payload = WEB_ANALYSTICS_API_TOP_STATS_PAYLOAD + base_payload["date_range"] = [str(self.period.lower), str(self.period.upper)] + return base_payload def populate_from_api(self): """Fetch stats from API and generate child WebsiteStatItem instances.""" - response = requests.get(self.analytics_api_url) + if not settings.PLAUSIBLE_STATS_KEY: + logger.info("Plausible API key not set, skipping") + return + + headers = { + "Content-Type": "application/json; charset=utf-8", + "Authorization": f"Bearer {settings.PLAUSIBLE_STATS_KEY}", + } + response = requests.post( + url=WEB_ANALYTICS_API_URL_V2, + json=self.analytics_api_payload, + headers=headers, + ) data = response.json() - if not data or "top_stats" not in data: + if not data or "results" not in data or "query" not in data: raise ValueError(f"Invalid Plausible API response: {data}") # Clear existing stat items @@ -53,12 +75,16 @@ def populate_from_api(self): stat_items = [] - for stat_data in data["top_stats"]: + for i, value in enumerate(data["query"]["metrics"]): + code_name = value + name = WEB_ANALYTICS_CODENAME_MAPPING.get(code_name, "") + met_value = data["results"][0]["metrics"][i] + stat = WebsiteStatItem( report=self, - name=stat_data["name"], - value=stat_data["value"], - code_name=stat_data["graph_metric"], + name=name, + value=met_value, + code_name=code_name, ) stat_items.append(stat)