Add report action

This commit is contained in:
Sergey Motornyuk 2022-05-07 01:59:15 +03:00
parent 86ec2858a5
commit 67b599ce1a
10 changed files with 99 additions and 73 deletions

View File

@ -49,7 +49,6 @@ jobs:
- uses: actions/checkout@v2
- name: Install requirements
run: |
pip install -r requirements.txt
pip install -r dev-requirements.txt
pip install -e .
# Replace default path to CKAN core config file with the one on the container

View File

@ -11,9 +11,13 @@ groups:
- key: googleanalytics.account
- key: googleanalytics.profile_id
- key: googleanalytics.domain
default: auto
- key: googleanalytics.credentials.path
- key: googleanalytics.fields
default: "{}"

View File

@ -1,44 +1,35 @@
import httplib2
from apiclient.discovery import build
from oauth2client.service_account import ServiceAccountCredentials
from ckanext.googleanalytics import utils
def _prepare_credentials(credentials_filename):
"""
Either returns the user's oauth credentials or uses the credentials
file to generate a token (by forcing the user to login in the browser)
"""
scope = ["https://www.googleapis.com/auth/analytics.readonly"]
credentials = ServiceAccountCredentials.from_json_keyfile_name(
credentials_filename, scopes=scope
)
return credentials
from . import utils
def init_service(credentials_file):
"""
Given a file containing the user's oauth token (and another with
credentials in case we need to generate the token) will return a
service object representing the analytics API.
"""
http = httplib2.Http()
"""Get a service that communicates to a Google API."""
scope = ["https://www.googleapis.com/auth/analytics.readonly"]
credentials = ServiceAccountCredentials.from_json_keyfile_name(
credentials_file, scopes=scope
)
credentials = _prepare_credentials(credentials_file)
http = credentials.authorize(http) # authorize the http object
return build("analytics", "v3", http=http)
return build("analytics", "v3", credentials=credentials)
def get_profile_id(service):
"""
"""Get static profile ID or fetch one from the service.
Get the profile ID for this user and the service specified by the
'googleanalytics.id' configuration option. This function iterates
over all of the accounts available to the user who invoked the
service to find one where the account name matches (in case the
user has several).
If not user configured, the first account is used
"""
profile_id = utils.config_profile_id()
if profile_id:
return profile_id
accounts = service.management().accounts().list().execute()
if not accounts.get("items"):
@ -46,14 +37,9 @@ def get_profile_id(service):
accountName = utils.config_account()
webPropertyId = utils.config_id()
for acc in accounts.get("items"):
if acc.get("name") == accountName:
if not accountName or acc.get("name") == accountName:
accountId = acc.get("id")
# TODO: check, whether next line is doing something useful.
service.management().webproperties().list(
accountId=accountId
).execute()
profiles = (
service.management()
.profiles()

View File

@ -1,28 +0,0 @@
header_code = """
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', '%s']);
_gaq.push(['_setDomainName', '%s']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script');
ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ?
'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s);
})();
</script>
"""
footer_code = """
<script type="text/javascript" src="%s"></script>
"""
download_style = """
<style type="text/css">
span.downloads-count {
font-size: 0.9em;
}
</style>
"""

View File

@ -4,19 +4,22 @@ import ckan.plugins.toolkit as tk
from ckan.logic import validate
from . import schema
from .. import utils
from ..model import PackageStats, ResourceStats
from ..ga_auth import init_service, get_profile_id
def get_actions():
return dict(
googleanalytics_package_stats_show=googleanalytics_package_stats_show,
googleanalytics_resource_stats_show=googleanalytics_resource_stats_show
googleanalytics_package_stats_show=package_stats_show,
googleanalytics_resource_stats_show=resource_stats_show,
googleanalytics_event_report=event_report,
)
@validate(schema.googleanalytics_package_stats_show)
@validate(schema.package_stats_show)
@tk.side_effect_free
def googleanalytics_package_stats_show(context, data_dict):
def package_stats_show(context, data_dict):
tk.check_access("googleanalytics_package_stats_show", context, data_dict)
rec = (
context["session"]
@ -31,9 +34,9 @@ def googleanalytics_package_stats_show(context, data_dict):
return rec.for_json(context)
@validate(schema.googleanalytics_resource_stats_show)
@validate(schema.resource_stats_show)
@tk.side_effect_free
def googleanalytics_resource_stats_show(context, data_dict):
def resource_stats_show(context, data_dict):
tk.check_access("googleanalytics_resource_stats_show", context, data_dict)
rec = (
context["session"]
@ -46,3 +49,35 @@ def googleanalytics_resource_stats_show(context, data_dict):
raise tk.ObjectNotFound()
return rec.for_json(context)
@validate(schema.event_report)
@tk.side_effect_free
def event_report(context, data_dict):
tk.check_access("sysadmin", context, data_dict)
se = init_service(utils.config_credentials())
filters = "ga:eventAction=={action};ga:eventCategory=={category}".format(
action=data_dict["action"], category=data_dict["category"]
)
if "label" in data_dict:
filters += ";ga:eventLabel=={label}".format(label=data_dict["label"])
report = (
se.data()
.ga()
.get(
ids="ga:{id}".format(id=get_profile_id(se)),
dimensions=",".join(data_dict["dimensions"]),
metrics=",".join(data_dict["metrics"]),
start_date=data_dict["start_date"].date().isoformat(),
end_date=data_dict["end_date"].date().isoformat(),
filters=filters,
)
.execute()
)
return {
"headers": [h["name"] for h in report["columnHeaders"]],
"rows": report["rows"],
}

View File

@ -5,14 +5,14 @@ from ckan.authz import is_authorized
def get_auth():
return dict(
googleanalytics_package_stats_show=googleanalytics_package_stats_show,
googleanalytics_resource_stats_show=googleanalytics_resource_stats_show
googleanalytics_package_stats_show=package_stats_show,
googleanalytics_resource_stats_show=resource_stats_show,
)
def googleanalytics_package_stats_show(context, data_dict):
def package_stats_show(context, data_dict):
return {"success": is_authorized("package_show", context, data_dict)}
def googleanalytics_resource_stats_show(context, data_dict):
def resource_stats_show(context, data_dict):
return {"success": is_authorized("resource_show", context, data_dict)}

View File

@ -2,10 +2,31 @@ from ckan.logic.schema import validator_args
@validator_args
def googleanalytics_package_stats_show(not_empty):
def package_stats_show(not_empty):
return {"id": [not_empty]}
@validator_args
def googleanalytics_resource_stats_show(not_empty):
def resource_stats_show(not_empty):
return {"id": [not_empty]}
@validator_args
def event_report(
not_empty, isodate, ignore_missing, json_list_or_string, default
):
return {
"start_date": [not_empty, isodate],
"end_date": [not_empty, isodate],
"category": [not_empty],
"action": [not_empty],
"label": [ignore_missing, not_empty],
"dimensions": [
default("ga:eventCategory,ga:eventAction,ga:eventLabel"),
json_list_or_string,
],
"metrics": [
default("ga:totalEvents,ga:uniqueEvents,ga:eventValue"),
json_list_or_string,
],
}

View File

@ -14,6 +14,14 @@ def config_account():
return tk.config.get("googleanalytics.account")
def config_profile_id():
return tk.config.get("googleanalytics.profile_id")
def config_credentials():
return tk.config.get("googleanalytics.credentials.path")
def config_domain():
return tk.config.get("googleanalytics.domain", "auto")

View File

@ -1,6 +1,6 @@
[metadata]
name = ckanext-googleanalytics
version = 2.0.8
version = 2.1.0
description = Add GA tracking and reporting to CKAN instance
long_description = file: README.md
long_description_content_type = text/markdown
@ -23,6 +23,7 @@ keywords =
# python_requires = >= 3.7
install_requires =
ckantoolkit
google-api-python-client
packages = find:
namespace_packages = ckanext