forked from getsentry/sentry
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Katie Lundsgaard
authored
Jan 27, 2017
1 parent
4243142
commit ae9051f
Showing
7 changed files
with
287 additions
and
9 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
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 |
---|---|---|
|
@@ -4,12 +4,116 @@ | |
|
||
from django.db.models import Sum | ||
|
||
|
||
from collections import Counter, defaultdict | ||
|
||
from sentry.api.serializers import Serializer, register, serialize | ||
from sentry.models import Release, ReleaseProject, TagValue | ||
from sentry.db.models.query import in_iexact | ||
from sentry.models import Release, ReleaseCommit, ReleaseProject, TagValue, User, UserEmail | ||
|
||
|
||
@register(Release) | ||
class ReleaseSerializer(Serializer): | ||
def _get_users_for_commits(self, release_commits, org_id): | ||
""" | ||
Returns a dictionary of author_id => user, if a Sentry | ||
user object exists for that email. If there is no matching | ||
Sentry user, a {user, email} dict representation of that | ||
author is returned. | ||
e.g. | ||
{ | ||
1: serialized(<User id=1>), | ||
2: {email: '[email protected]', name: 'dunno'}, | ||
... | ||
} | ||
""" | ||
authors = set(rc.commit.author for rc in release_commits if rc.commit.author is not None) | ||
if not len(authors): | ||
return {} | ||
|
||
# Filter users based on the emails provided in the commits | ||
user_emails = UserEmail.objects.filter( | ||
in_iexact('email', [a.email for a in authors]), | ||
).order_by('id') | ||
|
||
# Filter users belonging to the organization associated with | ||
# the release | ||
users = User.objects.filter( | ||
id__in=[ue.user_id for ue in user_emails], | ||
sentry_orgmember_set__organization_id=org_id | ||
) | ||
users_by_id = dict((user.id, serialize(user)) for user in users) | ||
|
||
# Figure out which email address matches to a user | ||
users_by_email = {} | ||
for user_email in user_emails: | ||
if user_email.email in users_by_email: | ||
pass | ||
|
||
user = users_by_id.get(user_email.user_id) | ||
if user: | ||
users_by_email[user_email.email] = user | ||
|
||
author_objs = {} | ||
for author in authors: | ||
author_objs[author.id] = users_by_email.get(author.email, { | ||
"name": author.name, | ||
"email": author.email | ||
}) | ||
|
||
return author_objs | ||
|
||
def _get_commit_metadata(self, item_list, user): | ||
""" | ||
Returns a dictionary of release_id => commit metadata, | ||
where each commit metadata dict contains commit_count | ||
and an array of authors. | ||
e.g. | ||
{ | ||
1: { | ||
'commit_count': 3, | ||
'authors': [<User id=1>, <User id=2>] | ||
}, | ||
... | ||
} | ||
If there are no commits, returns None. | ||
""" | ||
|
||
release_commits = list(ReleaseCommit.objects.filter( | ||
release__in=item_list).select_related("commit", "commit__author")) | ||
|
||
if not len(release_commits): | ||
return None | ||
|
||
org_ids = set(item.organization_id for item in item_list) | ||
assert len(org_ids) == 1 | ||
org_id = org_ids.pop() | ||
|
||
users_by_email = self._get_users_for_commits(release_commits, org_id) | ||
commit_count_by_release_id = Counter() | ||
authors_by_release_id = defaultdict(dict) | ||
|
||
for rc in release_commits: | ||
# Accumulate authors per release | ||
author = rc.commit.author | ||
if author: | ||
authors_by_release_id[rc.release_id][author.id] = \ | ||
users_by_email[author.id] | ||
|
||
# Increment commit count per release | ||
commit_count_by_release_id[rc.release_id] += 1 | ||
|
||
result = {} | ||
for item in item_list: | ||
result[item] = { | ||
'commit_count': commit_count_by_release_id[item.id], | ||
'authors': authors_by_release_id.get(item.id, {}).values(), | ||
} | ||
return result | ||
|
||
def get_attrs(self, item_list, user, *args, **kwargs): | ||
tags = { | ||
tk.value: tk | ||
|
@@ -41,13 +145,20 @@ def get_attrs(self, item_list, user, *args, **kwargs): | |
.values_list('release_id', 'new_groups') | ||
) | ||
|
||
release_metadata_attrs = self._get_commit_metadata(item_list, user) | ||
|
||
result = {} | ||
for item in item_list: | ||
result[item] = { | ||
'tag': tags.get(item.version), | ||
'owner': owners[six.text_type(item.owner_id)] if item.owner_id else None, | ||
'new_groups': group_counts_by_release.get(item.id) or 0 | ||
'new_groups': group_counts_by_release.get(item.id) or 0, | ||
'commit_count': 0, | ||
'authors': [], | ||
} | ||
if release_metadata_attrs: | ||
result[item].update(release_metadata_attrs[item]) | ||
|
||
return result | ||
|
||
def serialize(self, obj, attrs, user, *args, **kwargs): | ||
|
@@ -62,6 +173,8 @@ def serialize(self, obj, attrs, user, *args, **kwargs): | |
'data': obj.data, | ||
'newGroups': attrs['new_groups'], | ||
'owner': attrs['owner'], | ||
'commitCount': attrs.get('commit_count', 0), | ||
'authors': attrs.get('authors', []), | ||
} | ||
if attrs['tag']: | ||
d.update({ | ||
|
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 |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import React from 'react'; | ||
import Avatar from './avatar'; | ||
import TooltipMixin from '../mixins/tooltip'; | ||
import {t} from '../locale'; | ||
|
||
const ReleaseStats = React.createClass({ | ||
propTypes: { | ||
release: React.PropTypes.object, | ||
}, | ||
|
||
mixins: [ | ||
TooltipMixin({ | ||
selector: '.tip' | ||
}), | ||
], | ||
|
||
render() { | ||
let release = this.props.release; | ||
let commitCount = release.commitCount; | ||
let authorCount = release.authors.length; | ||
if (commitCount === 0) { | ||
return null; | ||
} | ||
return ( | ||
<div className="release-info"> | ||
<div><b>{commitCount}{t(' commits by ')}{authorCount}{t(' authors')}</b></div> | ||
{release.authors.map(author => { | ||
return ( | ||
<span className="assignee-selector tip" | ||
title={author.name + ' ' + author.email}> | ||
<Avatar user={author}/> | ||
</span> | ||
); | ||
})} | ||
</div> | ||
); | ||
} | ||
}); | ||
|
||
export default ReleaseStats; |
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
Oops, something went wrong.