From 948df4999c68f1e5a72cb36b8760565b731907d1 Mon Sep 17 00:00:00 2001 From: Matthew Elwell Date: Fri, 10 May 2019 10:42:09 +0100 Subject: [PATCH] usage endpoint --- readme.md | 2 ++ src/analytics/middleware.py | 2 +- src/analytics/query.py | 37 ++++++++++++++++++++++++++++ src/analytics/{utils.py => track.py} | 0 src/app/settings/common.py | 6 +++++ src/features/views.py | 8 +++--- src/organisations/models.py | 4 +++ src/organisations/views.py | 15 +++++++++++ 8 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 src/analytics/query.py rename src/analytics/{utils.py => track.py} (100%) diff --git a/readme.md b/readme.md index 054203bf496d..2f25c0b4ce9e 100644 --- a/readme.md +++ b/readme.md @@ -112,6 +112,8 @@ The application relies on the following environment variables to run: * `DATABASE_URL`: required by develop and master environments, should be a standard format database url e.g. postgres://user:password@host:port/db_name * `DJANGO_SECRET_KEY`: see 'Creating a secret key' section below * `GOOGLE_ANALYTICS_KEY`: if google analytics is required, add your tracking code +* `GOOGLE_SERVICE_ACCOUNT`: service account json for accessing the google API, used for getting usage of an organisation - needs access to analytics.readonly scope +* `GA_TABLE_ID`: GA table ID (view) to query when looking for organisation usage ### Creating a secret key It is important to also set an environment variable on whatever platform you are using for diff --git a/src/analytics/middleware.py b/src/analytics/middleware.py index ac7a50997482..2f986b6a45e9 100644 --- a/src/analytics/middleware.py +++ b/src/analytics/middleware.py @@ -1,6 +1,6 @@ from threading import Thread -from .utils import track_request +from .track import track_request class GoogleAnalyticsMiddleware: diff --git a/src/analytics/query.py b/src/analytics/query.py new file mode 100644 index 000000000000..8014f6f972b1 --- /dev/null +++ b/src/analytics/query.py @@ -0,0 +1,37 @@ +import json + +from apiclient.discovery import build +from django.conf import settings +from google.oauth2 import service_account + +GA_SCOPES = ['https://www.googleapis.com/auth/analytics.readonly'] +GA_API_NAME = 'analytics' +GA_API_VERSION = 'v3' + + +def get_service(): + """ + Get the google service object to use to query the API + """ + credentials = service_account.Credentials.from_service_account_info( + json.loads(settings.GOOGLE_SERVICE_ACCOUNT), scopes=GA_SCOPES) + + # Build the service object. + return build(GA_API_NAME, GA_API_VERSION, credentials=credentials) + + +def get_events_for_organisation(organisation): + """ + Get number of tracked events for last 30 days for an organisation + + :return: number of events as integer + """ + ga_response = get_service().data().ga().get( + ids=settings.GA_TABLE_ID, + start_date='30daysAgo', + end_date='today', + metrics='ga:totalEvents', + dimensions='ga:date', + filters=f'ga:eventCategory=={organisation.get_unique_slug()}').execute() + + return int(ga_response['totalsForAllResults']['ga:totalEvents']) diff --git a/src/analytics/utils.py b/src/analytics/track.py similarity index 100% rename from src/analytics/utils.py rename to src/analytics/track.py diff --git a/src/app/settings/common.py b/src/app/settings/common.py index 819bb8659a2a..17575c766c7c 100644 --- a/src/app/settings/common.py +++ b/src/app/settings/common.py @@ -35,6 +35,12 @@ # Google Analytics Configuration GOOGLE_ANALYTICS_KEY = os.environ.get('GOOGLE_ANALYTICS_KEY', '') +GOOGLE_SERVICE_ACCOUNT = os.environ.get('GOOGLE_SERVICE_ACCOUNT') +if not GOOGLE_SERVICE_ACCOUNT: + warnings.warn("GOOGLE_SERVICE_ACCOUNT not configured, getting organisation usage will not work") +GA_TABLE_ID = os.environ.get('GA_TABLE_ID') +if not GA_TABLE_ID: + warnings.warn("GA_TABLE_ID not configured, getting organisation usage will not work") if 'DJANGO_ALLOWED_HOSTS' in os.environ: ALLOWED_HOSTS = os.environ['DJANGO_ALLOWED_HOSTS'].split(',') diff --git a/src/features/views.py b/src/features/views.py index aef9803eee16..dc68154db31a 100644 --- a/src/features/views.py +++ b/src/features/views.py @@ -8,7 +8,7 @@ from rest_framework.response import Response from rest_framework.schemas import AutoSchema -from analytics.utils import track_event +from analytics.track import track_event from environments.models import Environment, Identity from projects.models import Project from .models import FeatureState, Feature @@ -223,17 +223,15 @@ def get(self, request, identifier=None, *args, **kwargs): return Response(error_response, status=status.HTTP_400_BAD_REQUEST) - ga_event_category = str(environment.project.organisation.id) + "-" + environment.project.organisation.name - if identifier: - track_event(ga_event_category, "identity_flags") + track_event(environment.project.organisation.get_unique_slug(), "identity_flags") identity, _ = Identity.objects.get_or_create( identifier=identifier, environment=environment, ) else: - track_event(ga_event_category, "flags") + track_event(environment.project.organisation.get_unique_slug(), "flags") identity = None kwargs = { diff --git a/src/organisations/models.py b/src/organisations/models.py index fb5c39c566ad..a7d5bc1d73d2 100644 --- a/src/organisations/models.py +++ b/src/organisations/models.py @@ -16,8 +16,12 @@ class Organisation(models.Model): free_to_use_subscription = models.BooleanField(default=True) plan = models.CharField(max_length=20, null=True, blank=True) subscription_date = models.DateTimeField('SubscriptionDate', blank=True, null=True) + class Meta: ordering = ['id'] def __str__(self): return "Org %s" % self.name + + def get_unique_slug(self): + return str(self.id) + "-" + self.name diff --git a/src/organisations/views.py b/src/organisations/views.py index a7290fc53052..7aceefe5f773 100644 --- a/src/organisations/views.py +++ b/src/organisations/views.py @@ -6,6 +6,7 @@ from rest_framework.exceptions import ValidationError from rest_framework.response import Response +from analytics.query import get_events_for_organisation from projects.serializers import ProjectSerializer from organisations.serializers import OrganisationSerializer from users.models import Invite @@ -74,6 +75,20 @@ def invite(self, request, pk): else: raise ValidationError(invites_serializer.errors) + @action(detail=True, methods=["GET"]) + def usage(self, request, pk): + organisation = self.get_object() + + try: + events = get_events_for_organisation(organisation) + except (TypeError, ValueError): + # TypeError can be thrown when getting service account if not configured + # ValueError can be thrown if GA returns a value that cannot be converted to integer + return Response({"error": "Couldn't get number of events for organisation."}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + return Response({"events": events}, status=status.HTTP_200_OK) + class InviteViewSet(viewsets.ModelViewSet): serializer_class = InviteListSerializer