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
Moved analytics configuration to the database. #551
Merged
+208
−156
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
acbe7b1
Moved analytics configuration to the database.
aslagle 2c02854
Merge remote-tracking branch 'origin/multi-tenant' into analytics-in-db
aslagle bc916dd
Added a missing sitewide setting.
aslagle 41a97f0
Put analytics in ReplacementPolicy.
aslagle 19714a0
Fixed import.
aslagle 810f08e
Changed Analytics to store providers by library id.
aslagle 60b2de4
Merge remote-tracking branch 'origin/multi-tenant' into analytics-in-db
aslagle File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like this, but I'm not sure anything else would be better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is a performance problem. We're looking at database queries and module imports every time we want to change a LicensePool. I think
analytics
should be passed intoReplacementPolicy
just likemirror
is. That should cut down on the number of times we have to instantiate a newAnalytics
object.