Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(app-platform): Analytics #12718

Merged
merged 1 commit into from
Apr 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/sentry/analytics/events/sentry_app_created.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from __future__ import absolute_import

from sentry import analytics


class SentryAppCreatedEvent(analytics.Event):
type = 'sentry_app.created'

attributes = (
analytics.Attribute('user_id'),
analytics.Attribute('organization_id'),
analytics.Attribute('sentry_app'),
)


analytics.register(SentryAppCreatedEvent)
16 changes: 16 additions & 0 deletions src/sentry/analytics/events/sentry_app_deleted.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from __future__ import absolute_import

from sentry import analytics


class SentryAppDeletedEvent(analytics.Event):
type = 'sentry_app.deleted'

attributes = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason we don't want the organization_id for this event? (for update app as well)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can derive the Org from the SentryApp, so I figured we didn't need it here.

analytics.Attribute('user_id'),
analytics.Attribute('organization_id'),
analytics.Attribute('sentry_app'),
)


analytics.register(SentryAppDeletedEvent)
16 changes: 16 additions & 0 deletions src/sentry/analytics/events/sentry_app_installed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from __future__ import absolute_import

from sentry import analytics


class SentryAppInstalledEvent(analytics.Event):
type = 'sentry_app.installed'

attributes = (
analytics.Attribute('user_id'),
analytics.Attribute('organization_id'),
analytics.Attribute('sentry_app'),
)


analytics.register(SentryAppInstalledEvent)
15 changes: 15 additions & 0 deletions src/sentry/analytics/events/sentry_app_token_exchanged.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from __future__ import absolute_import

from sentry import analytics


class SentryAppTokenExchangedEvent(analytics.Event):
type = 'sentry_app.token_exchanged'

attributes = (
analytics.Attribute('sentry_app_installation_id'),
analytics.Attribute('exchange_type'),
)


analytics.register(SentryAppTokenExchangedEvent)
16 changes: 16 additions & 0 deletions src/sentry/analytics/events/sentry_app_uninstalled.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from __future__ import absolute_import

from sentry import analytics


class SentryAppUninstalledEvent(analytics.Event):
type = 'sentry_app.uninstalled'

attributes = (
analytics.Attribute('user_id'),
analytics.Attribute('organization_id'),
analytics.Attribute('sentry_app'),
)


analytics.register(SentryAppUninstalledEvent)
15 changes: 15 additions & 0 deletions src/sentry/analytics/events/sentry_app_updated.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from __future__ import absolute_import

from sentry import analytics


class SentryAppUpdatedEvent(analytics.Event):
type = 'sentry_app.updated'

attributes = (
analytics.Attribute('user_id'),
analytics.Attribute('sentry_app'),
)


analytics.register(SentryAppUpdatedEvent)
2 changes: 2 additions & 0 deletions src/sentry/api/endpoints/sentry_app_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def put(self, request, sentry_app):
result = serializer.object

updated_app = Updater.run(
user=request.user,
sentry_app=sentry_app,
name=result.get('name'),
author=result.get('author'),
Expand All @@ -61,6 +62,7 @@ def delete(self, request, sentry_app):

if sentry_app.status == SentryAppStatus.UNPUBLISHED:
Destroyer.run(
user=request.user,
sentry_app=sentry_app,
request=request,
)
Expand Down
1 change: 1 addition & 0 deletions src/sentry/api/endpoints/sentry_apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def post(self, request, organization):

sentry_app = Creator.run(
name=result.get('name'),
user=request.user,
author=result.get('author'),
organization=self._get_user_org(request),
scopes=result.get('scopes'),
Expand Down
5 changes: 5 additions & 0 deletions src/sentry/mediators/mediator.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ def run(cls, *args, **kwargs):
with obj.log():
result = obj.call()
obj.audit()
obj.record_analytics()
return result

def __init__(self, *args, **kwargs):
Expand All @@ -172,6 +173,10 @@ def audit(self):
# used for creating audit log entries
pass

def record_analytics(self):
# used to record data to Amplitude
pass

def call(self):
raise NotImplementedError

Expand Down
9 changes: 9 additions & 0 deletions src/sentry/mediators/sentry_app_installations/creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import six

from sentry import analytics
from sentry.mediators import Mediator, Param, service_hooks
from sentry.models import (
AuditLogEntryEvent, ApiAuthorization, ApiGrant, SentryApp, SentryAppInstallation
Expand Down Expand Up @@ -72,6 +73,14 @@ def audit(self):
},
)

def record_analytics(self):
analytics.record(
'sentry_app.installed',
user_id=self.user.id,
organization_id=self.organization.id,
sentry_app=self.slug,
)

@memoize
def api_application(self):
return self.sentry_app.application
Expand Down
9 changes: 9 additions & 0 deletions src/sentry/mediators/sentry_app_installations/destroyer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import absolute_import

from sentry import analytics
from sentry.mediators import Mediator, Param
from sentry.mediators import service_hooks
from sentry.models import AuditLogEntryEvent, ServiceHook
Expand Down Expand Up @@ -54,3 +55,11 @@ def audit(self):
'sentry_app': self.install.sentry_app.name,
},
)

def record_analytics(self):
analytics.record(
'sentry_app.uninstalled',
user_id=self.user.id,
organization_id=self.install.organization_id,
sentry_app=self.install.sentry_app.slug,
)
18 changes: 14 additions & 4 deletions src/sentry/mediators/sentry_apps/creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from collections import Iterable

from sentry import analytics
from sentry.utils.audit import create_audit_entry
from sentry.mediators import Mediator, Param
from sentry.models import (AuditLogEntryEvent, ApiApplication, SentryApp, SentryAppComponent, User,)
Expand All @@ -21,13 +22,14 @@ class Creator(Mediator):
schema = Param(dict, default=lambda self: {})
overview = Param(six.string_types, required=False)
request = Param('rest_framework.request.Request', required=False)
user = Param('sentry.models.User')

def call(self):
self.proxy = self._create_proxy_user()
self.api_app = self._create_api_application()
self.app = self._create_sentry_app()
self.sentry_app = self._create_sentry_app()
self._create_ui_components()
return self.app
return self.sentry_app

def _create_proxy_user(self):
return User.objects.create(
Expand Down Expand Up @@ -64,7 +66,7 @@ def _create_ui_components(self):
for element in schema.get('elements', []):
SentryAppComponent.objects.create(
type=element['type'],
sentry_app_id=self.app.id,
sentry_app_id=self.sentry_app.id,
schema=element,
)

Expand All @@ -76,6 +78,14 @@ def audit(self):
target_object=self.organization.id,
event=AuditLogEntryEvent.SENTRY_APP_ADD,
data={
'sentry_app': self.app.name,
'sentry_app': self.sentry_app.name,
},
)

def record_analytics(self):
analytics.record(
'sentry_app.created',
user_id=self.user.id,
organization_id=self.organization.id,
sentry_app=self.sentry_app.slug,
)
9 changes: 9 additions & 0 deletions src/sentry/mediators/sentry_apps/destroyer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import absolute_import

from sentry import analytics
from sentry.mediators import Mediator, Param
from sentry.mediators import sentry_app_installations
from sentry.models import AuditLogEntryEvent
Expand Down Expand Up @@ -44,3 +45,11 @@ def audit(self):
'sentry_app': self.sentry_app.name,
},
)

def record_analytics(self):
analytics.record(
'sentry_app.deleted',
user_id=self.user.id,
organization_id=self.sentry_app.owner.id,
sentry_app=self.sentry_app.slug,
)
11 changes: 10 additions & 1 deletion src/sentry/mediators/sentry_apps/updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import six

from collections import Iterable
from sentry.coreapi import APIError

from sentry import analytics
from sentry.coreapi import APIError
from sentry.constants import SentryAppStatus
from sentry.mediators import Mediator, Param
from sentry.mediators import service_hooks
Expand All @@ -23,6 +24,7 @@ class Updater(Mediator):
is_alertable = Param(bool, required=False)
schema = Param(dict, required=False)
overview = Param(six.string_types, required=False)
user = Param('sentry.models.User')

def call(self):
self._update_name()
Expand Down Expand Up @@ -103,3 +105,10 @@ def _create_ui_components(self):
sentry_app_id=self.sentry_app.id,
schema=element,
)

def record_analytics(self):
analytics.record(
'sentry_app.updated',
user_id=self.user.id,
sentry_app=self.sentry_app.slug,
)
12 changes: 11 additions & 1 deletion src/sentry/mediators/token_exchange/grant_exchanger.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from datetime import datetime

from sentry import analytics
from sentry.coreapi import APIUnauthorized
from sentry.mediators import Mediator, Param
from sentry.mediators.token_exchange.validator import Validator
Expand All @@ -29,13 +30,22 @@ def call(self):
# exchangable, so we delete it.
self._delete_grant()

return ApiToken.objects.create(
self.token = ApiToken.objects.create(
user=self.user,
application=self.application,
scope_list=self.sentry_app.scope_list,
expires_at=token_expiration()
)

return self.token

def record_analytics(self):
analytics.record(
'sentry_app.token_exchanged',
sentry_app_installation_id=self.install.id,
exchange_type='authorization',
)

def _validate(self):
Validator.run(
install=self.install,
Expand Down
8 changes: 8 additions & 0 deletions src/sentry/mediators/token_exchange/refresher.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from datetime import datetime

from sentry import analytics
from sentry.coreapi import APIUnauthorized
from sentry.mediators import Mediator, Param
from sentry.mediators.token_exchange.validator import Validator
Expand Down Expand Up @@ -34,6 +35,13 @@ def call(self):
expires_at=token_expiration(),
)

def record_analytics(self):
analytics.record(
'sentry_app.token_exchanged',
sentry_app_installation_id=self.install.id,
exchange_type='refresh',
)

def _validate(self):
Validator.run(
install=self.install,
Expand Down
3 changes: 2 additions & 1 deletion src/sentry/testutils/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,7 @@ def add_user_permission(user, permission):

@staticmethod
def create_sentry_app(name=None, author='Sentry', organization=None, published=False, scopes=(),
webhook_url=None, **kwargs):
webhook_url=None, user=None, **kwargs):
if not name:
name = petname.Generate(2, ' ', letters=10).title()
if not organization:
Expand All @@ -682,6 +682,7 @@ def create_sentry_app(name=None, author='Sentry', organization=None, published=F
webhook_url = 'https://example.com/webhook'

_kwargs = {
'user': (user or Factories.create_user()),
'name': name,
'organization': organization,
'author': author,
Expand Down
2 changes: 1 addition & 1 deletion tests/sentry/api/endpoints/test_sentry_app_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def test_cannot_update_name_with_non_unique_slug(self):
organization=self.org,
)

sentry_apps.Destroyer.run(sentry_app=sentry_app)
sentry_apps.Destroyer.run(sentry_app=sentry_app, user=self.user)

response = self.client.put(
self.url,
Expand Down
2 changes: 1 addition & 1 deletion tests/sentry/api/endpoints/test_sentry_apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def test_non_unique_app_slug(self):
name='Foo Bar',
organization=self.org,
)
sentry_apps.Destroyer.run(sentry_app=sentry_app)
sentry_apps.Destroyer.run(sentry_app=sentry_app, user=self.user)
response = self._post(**{'name': sentry_app.name})
assert response.status_code == 422
assert response.data == \
Expand Down
16 changes: 16 additions & 0 deletions tests/sentry/mediators/sentry_app_installations/test_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,19 @@ def test_associations(self):

assert install.api_grant is not None
assert install.authorization is not None

@patch('sentry.analytics.record')
def test_records_analytics(self, record):
Creator.run(
organization=self.org,
slug='nulldb',
user=self.user,
request=self.make_request(user=self.user, method='GET'),
)

record.assert_called_with(
'sentry_app.installed',
user_id=self.user.id,
organization_id=self.org.id,
sentry_app='nulldb',
)
Loading