This repository has been archived by the owner on Apr 11, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #551 from NYPL-Simplified/analytics-in-db
Moved analytics configuration to the database.
- Loading branch information
Showing
11 changed files
with
208 additions
and
156 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,71 +1,41 @@ | ||
from nose.tools import set_trace | ||
import importlib | ||
import contextlib | ||
import datetime | ||
from config import Configuration | ||
from collections import defaultdict | ||
from model import ExternalIntegration | ||
from config import CannotLoadConfiguration | ||
|
||
class Analytics(object): | ||
|
||
__instance = None | ||
|
||
if '.' in __module__: | ||
# We are operating in an application that imports this product | ||
# as a package (probably called 'core'). The module name of | ||
# the analytics provider should be scoped to the name of the | ||
# package, i.e. 'core.local_analytics_provider'. | ||
package_name = __module__[:__module__.rfind('.')+1] | ||
else: | ||
# This application is not imported as a package, probably | ||
# because we're running its unit tests. | ||
package_name = '' | ||
|
||
DEFAULT_PROVIDERS = [package_name + "local_analytics_provider"] | ||
|
||
@classmethod | ||
def instance(cls): | ||
if not cls.__instance: | ||
config = Configuration.instance | ||
providers = cls.load_providers_from_config(config) | ||
cls.initialize(providers, config) | ||
return cls.__instance | ||
|
||
@classmethod | ||
def initialize(cls, providers, config): | ||
if not providers: | ||
cls.__instance = cls() | ||
return cls.__instance | ||
if isinstance(providers, basestring): | ||
providers = [providers] | ||
analytics_providers = [] | ||
for provider_string in providers: | ||
provider_module = importlib.import_module(provider_string) | ||
provider_class = getattr(provider_module, "Provider") | ||
analytics_providers.append(provider_class.from_config(config)) | ||
cls.__instance = cls(analytics_providers) | ||
return cls.__instance | ||
|
||
def __init__(self, providers=[]): | ||
self.providers = providers | ||
|
||
@classmethod | ||
def collect_event(cls, _db, license_pool, event_type, time=None, **kwargs): | ||
def __init__(self, _db): | ||
self.sitewide_providers = [] | ||
self.library_providers = defaultdict(list) | ||
self.initialization_exceptions = {} | ||
|
||
# Find a list of all the ExternalIntegrations set up with a | ||
# goal of analytics. | ||
integrations = _db.query(ExternalIntegration).filter(ExternalIntegration.goal==ExternalIntegration.ANALYTICS_GOAL) | ||
# Turn each integration into an analytics provider. | ||
for integration in integrations: | ||
try: | ||
provider_module = importlib.import_module(integration.protocol) | ||
provider_class = getattr(provider_module, "Provider", None) | ||
if provider_class: | ||
if not integration.libraries: | ||
provider = provider_class(integration) | ||
self.sitewide_providers.append(provider) | ||
else: | ||
for library in integration.libraries: | ||
provider = provider_class(integration, library) | ||
self.library_providers[library.id].append(provider) | ||
else: | ||
self.initialization_exceptions[integration.id] = "Module %s does not have Provider defined." % integration.protocol | ||
except (ImportError, CannotLoadConfiguration), e: | ||
self.initialization_exceptions[integration.id] = e | ||
|
||
def collect_event(self, library, license_pool, event_type, time=None, **kwargs): | ||
if not time: | ||
time = datetime.datetime.utcnow() | ||
for provider in cls.instance().providers: | ||
provider.collect_event(_db, license_pool, event_type, time, **kwargs) | ||
|
||
@classmethod | ||
def load_providers_from_config(cls, config): | ||
policies = config.get(Configuration.POLICIES, {}) | ||
return policies.get(Configuration.ANALYTICS_POLICY, cls.DEFAULT_PROVIDERS) | ||
|
||
|
||
@contextlib.contextmanager | ||
def temp_analytics(providers, config): | ||
"""A context manager to temporarily replace the analytics providers | ||
used by a test. | ||
""" | ||
old_instance = Analytics._Analytics__instance | ||
Analytics.initialize(providers, config) | ||
yield | ||
Analytics._Analytics__instance = old_instance | ||
|
||
for provider in (self.sitewide_providers + self.library_providers[library.id]): | ||
provider.collect_event(library, license_pool, event_type, time, **kwargs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,19 @@ | ||
from flask.ext.babel import lazy_gettext as _ | ||
from model import Session, CirculationEvent | ||
|
||
class LocalAnalyticsProvider(object): | ||
@classmethod | ||
def from_config(cls, config): | ||
return cls() | ||
NAME = _("Local Analytics") | ||
|
||
DESCRIPTION = _("Store analytics events in the 'circulationevents' database table.") | ||
|
||
def __init__(self, integration): | ||
self.integration_id = integration.id | ||
|
||
def collect_event(self, _db, license_pool, event_type, time, | ||
def collect_event(self, library, license_pool, event_type, time, | ||
old_value=None, new_value=None, **kwargs): | ||
from model import CirculationEvent | ||
_db = Session.object_session(library) | ||
|
||
CirculationEvent.log( | ||
_db, license_pool, event_type, old_value, new_value, start=time) | ||
|
||
Provider = LocalAnalyticsProvider | ||
Provider = LocalAnalyticsProvider |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,15 @@ | ||
class MockAnalyticsProvider(object): | ||
"""A mock analytics provider that keeps track of how many times it's called.""" | ||
|
||
@classmethod | ||
def from_config(cls, config): | ||
return cls(config.get('option')) | ||
|
||
def __init__(self, option=None): | ||
self.option = option | ||
def __init__(self, integration=None, library=None): | ||
self.count = 0 | ||
self.event = None | ||
if integration: | ||
self.url = integration.url | ||
|
||
def collect_event(self, _db, lp, event_type, time, **kwargs): | ||
def collect_event(self, library, lp, event_type, time=None, **kwargs): | ||
self.count = self.count + 1 | ||
self.event_type = event_type | ||
self.time = time | ||
|
||
Provider = MockAnalyticsProvider | ||
Provider = MockAnalyticsProvider |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.