From 3efdb8f12b24f0ec089eac22c5a2c8f9f658cd21 Mon Sep 17 00:00:00 2001 From: Ariel Rin Date: Fri, 10 Jan 2025 08:29:02 +0000 Subject: [PATCH] Docker/Baremetal Analytics --- allianceauth/analytics/admin.py | 7 +++--- .../0010_alter_analyticsidentifier_options.py | 17 ++++++++++++++ allianceauth/analytics/models.py | 22 ++++++++----------- allianceauth/analytics/tasks.py | 16 +++++++++----- allianceauth/analytics/tests/test_models.py | 15 ++----------- allianceauth/analytics/utils.py | 14 ++++++++++++ docs/features/core/analytics.md | 1 + 7 files changed, 58 insertions(+), 34 deletions(-) create mode 100644 allianceauth/analytics/migrations/0010_alter_analyticsidentifier_options.py diff --git a/allianceauth/analytics/admin.py b/allianceauth/analytics/admin.py index 383bbbe2..497f2d7c 100644 --- a/allianceauth/analytics/admin.py +++ b/allianceauth/analytics/admin.py @@ -1,15 +1,16 @@ from django.contrib import admin from .models import AnalyticsIdentifier, AnalyticsTokens +from solo.admin import SingletonModelAdmin @admin.register(AnalyticsIdentifier) -class AnalyticsIdentifierAdmin(admin.ModelAdmin): +class AnalyticsIdentifierAdmin(SingletonModelAdmin): search_fields = ['identifier', ] - list_display = ('identifier',) + list_display = ['identifier', ] @admin.register(AnalyticsTokens) class AnalyticsTokensAdmin(admin.ModelAdmin): search_fields = ['name', ] - list_display = ('name', 'type',) + list_display = ['name', 'type', ] diff --git a/allianceauth/analytics/migrations/0010_alter_analyticsidentifier_options.py b/allianceauth/analytics/migrations/0010_alter_analyticsidentifier_options.py new file mode 100644 index 00000000..7c1993e9 --- /dev/null +++ b/allianceauth/analytics/migrations/0010_alter_analyticsidentifier_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.16 on 2024-12-11 02:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('analytics', '0009_remove_analyticstokens_ignore_paths_and_more'), + ] + + operations = [ + migrations.AlterModelOptions( + name='analyticsidentifier', + options={'verbose_name': 'Analytics Identifier'}, + ), + ] diff --git a/allianceauth/analytics/models.py b/allianceauth/analytics/models.py index ddee495a..16ea0221 100644 --- a/allianceauth/analytics/models.py +++ b/allianceauth/analytics/models.py @@ -1,23 +1,19 @@ +from typing import Literal from django.db import models -from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ - +from solo.models import SingletonModel from uuid import uuid4 -class AnalyticsIdentifier(models.Model): +class AnalyticsIdentifier(SingletonModel): - identifier = models.UUIDField( - default=uuid4, - editable=False) + identifier = models.UUIDField(default=uuid4, editable=False) - def save(self, *args, **kwargs): - if not self.pk and AnalyticsIdentifier.objects.exists(): - # Force a single object - raise ValidationError('There is can be only one \ - AnalyticsIdentifier instance') - self.pk = self.id = 1 # If this happens to be deleted and recreated, force it to be 1 - return super().save(*args, **kwargs) + def __str__(self) -> Literal['Analytics Identifier']: + return "Analytics Identifier" + + class Meta: + verbose_name = "Analytics Identifier" class AnalyticsTokens(models.Model): diff --git a/allianceauth/analytics/tasks.py b/allianceauth/analytics/tasks.py index cc9ef160..d0a76c30 100644 --- a/allianceauth/analytics/tasks.py +++ b/allianceauth/analytics/tasks.py @@ -5,6 +5,7 @@ from django.apps import apps from celery import shared_task from .models import AnalyticsTokens, AnalyticsIdentifier from .utils import ( + existence_baremetal_or_docker, install_stat_addons, install_stat_tokens, install_stat_users) @@ -67,8 +68,8 @@ def analytics_event(namespace: str, value=value).apply_async(priority=9) -@shared_task() -def analytics_daily_stats(): +@shared_task +def analytics_daily_stats() -> None: """Celery Task: Do not call directly Gathers a series of daily statistics @@ -77,6 +78,7 @@ def analytics_daily_stats(): users = install_stat_users() tokens = install_stat_tokens() addons = install_stat_addons() + existence_type = existence_baremetal_or_docker() logger.debug("Running Daily Analytics Upload") analytics_event(namespace='allianceauth.analytics', @@ -84,6 +86,11 @@ def analytics_daily_stats(): label='existence', value=1, event_type='Stats') + analytics_event(namespace='allianceauth.analytics', + task='send_install_stats', + label=existence_type, + value=1, + event_type='Stats') analytics_event(namespace='allianceauth.analytics', task='send_install_stats', label='users', @@ -99,7 +106,6 @@ def analytics_daily_stats(): label='addons', value=addons, event_type='Stats') - for appconfig in apps.get_app_configs(): if appconfig.label in [ "django_celery_beat", @@ -135,7 +141,7 @@ def analytics_daily_stats(): event_type='Stats') -@shared_task() +@shared_task def send_ga_tracking_celery_event( measurement_id: str, secret: str, @@ -165,7 +171,7 @@ def send_ga_tracking_celery_event( } payload = { - 'client_id': AnalyticsIdentifier.objects.get(id=1).identifier.hex, + 'client_id': AnalyticsIdentifier.get_solo().identifier.hex, "user_properties": { "allianceauth_version": { "value": __version__ diff --git a/allianceauth/analytics/tests/test_models.py b/allianceauth/analytics/tests/test_models.py index 452c4eaa..979f2720 100644 --- a/allianceauth/analytics/tests/test_models.py +++ b/allianceauth/analytics/tests/test_models.py @@ -1,9 +1,8 @@ from allianceauth.analytics.models import AnalyticsIdentifier -from django.core.exceptions import ValidationError from django.test.testcases import TestCase -from uuid import UUID, uuid4 +from uuid import uuid4 # Identifiers @@ -14,14 +13,4 @@ uuid_2 = "7aa6bd70701f44729af5e3095ff4b55c" class TestAnalyticsIdentifier(TestCase): def test_identifier_random(self): - self.assertNotEqual(AnalyticsIdentifier.objects.get(), uuid4) - - def test_identifier_singular(self): - AnalyticsIdentifier.objects.all().delete() - AnalyticsIdentifier.objects.create(identifier=uuid_1) - # Yeah i have multiple asserts here, they all do the same thing - with self.assertRaises(ValidationError): - AnalyticsIdentifier.objects.create(identifier=uuid_2) - self.assertEqual(AnalyticsIdentifier.objects.count(), 1) - self.assertEqual(AnalyticsIdentifier.objects.get( - pk=1).identifier, UUID(uuid_1)) + self.assertNotEqual(AnalyticsIdentifier.get_solo(), uuid4) diff --git a/allianceauth/analytics/utils.py b/allianceauth/analytics/utils.py index e8c57927..fa50084f 100644 --- a/allianceauth/analytics/utils.py +++ b/allianceauth/analytics/utils.py @@ -1,3 +1,4 @@ +import os from django.apps import apps from allianceauth.authentication.models import User from esi.models import Token @@ -34,3 +35,16 @@ def install_stat_addons() -> int: The Number of Installed Apps""" addons = len(list(apps.get_app_configs())) return addons + + +def existence_baremetal_or_docker() -> str: + """Checks the Installation Type of an install + + Returns + ------- + str + existence_baremetal or existence_docker""" + docker_tag = os.getenv('AA_DOCKER_TAG') + if docker_tag: + return "existence_docker" + return "existence_baremetal" diff --git a/docs/features/core/analytics.md b/docs/features/core/analytics.md index d610edb6..d04c47a3 100644 --- a/docs/features/core/analytics.md +++ b/docs/features/core/analytics.md @@ -27,6 +27,7 @@ Analytics comes preloaded with our Google Analytics token, and the three types o Our Daily Stats contain the following: - A phone-in task to identify a server's existence +- A phone-in task to identify if a server is Bare-Metal or Dockerized - A task to send the Number of User models - A task to send the Number of Token Models - A task to send the Number of Installed Apps