-
Notifications
You must be signed in to change notification settings - Fork 887
[WIP] New Community Hub Stuff #2411
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
Closed
Closed
Changes from 2 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
f32ea1a
Switch from jingo-minify to django-pipeline
rehandalal 5c33cd8
fix travis
rehandalal 9e2242c
fix qunit
rehandalal 46e65b1
fix deploy
rehandalal d0785c0
use non cached storage
rehandalal d5b1de6
Use Bower for front-end dependencies
rehandalal 33ed827
fix deploy
rehandalal 191498e
[Bug 1139127] API for top contributors.
mythmon 07349ef
[Bug 1139128] Table UI for top questions contributors.
mythmon 062aa51
Merge remote-tracking branch 'rehandalal/bower' into community-hub
mythmon f550a2d
Convert Community Hub to server side rendering w/Browserify+Babel.
mythmon d387087
Add date filtering, cleanup.
mythmon 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 hidden or 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 |
|---|---|---|
|
|
@@ -11,6 +11,12 @@ | |
| 'less/community.less', | ||
| 'less/select.less', | ||
| ), | ||
| 'community-new': ( | ||
| 'css/font-awesome.css', | ||
| 'less/wiki-content.less', | ||
| 'less/community-new.less', | ||
| 'less/select.less', | ||
| ), | ||
| 'mobile/common': ( | ||
| 'css/normalize.css', | ||
| 'less/mobile/main.less', | ||
|
|
@@ -163,9 +169,19 @@ | |
| ), | ||
| 'community': ( | ||
| 'js/libs/jquery-1.10.1.min.js', | ||
| 'js/libs/jquery.placeholder.js', | ||
| 'js/main.js', | ||
| 'js/community.js', | ||
| 'js/select.js', | ||
| ), | ||
| 'community-new': ( | ||
| 'js/libs/jquery-1.10.1.min.js', | ||
| 'js/libs/jquery.placeholder.js', | ||
| 'js/libs/underscore.js', | ||
| 'js/libs/moment-2.8.3.js', | ||
| 'js/libs/react/react.js', | ||
| 'js/libs/react/JSXTransformer.js', | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Most of this shouldn't be here either, since it is duplicated from common. |
||
| ), | ||
| 'mobile/common': ( | ||
| 'js/i18n.js', | ||
| 'js/libs/underscore.js', | ||
|
|
||
This file contains hidden or 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,165 @@ | ||
| from collections import defaultdict | ||
| from datetime import datetime, timedelta | ||
|
|
||
| from elasticutils import F | ||
| from rest_framework import views | ||
| from rest_framework.response import Response | ||
|
|
||
| from kitsune.questions.models import AnswerMetricsMappingType | ||
| from kitsune.users.models import UserMappingType | ||
|
|
||
|
|
||
| # This should be the higher than the max number of contributors for a | ||
| # section. There isn't a way to tell ES to just return everything. | ||
| BIG_NUMBER = 10000 | ||
|
|
||
|
|
||
| class TopContributorsQuestions(views.APIView): | ||
|
|
||
| def get(self, request): | ||
| return Response(self.get_data(request)) | ||
|
|
||
| def get_filters(self): | ||
| f = F(by_asker=False) | ||
|
|
||
| for key, value in self.request.GET.items(): | ||
| filter_method = getattr(self, 'filter_' + key, lambda v: F()) | ||
| f &= filter_method(value) | ||
|
|
||
| start = datetime.now() - timedelta(days=90) | ||
| end = datetime.now() | ||
| f &= F(created__gte=start, created__lt=end) | ||
|
|
||
| return f | ||
|
|
||
| def filter_username(self, value): | ||
| username_lower = value.lower() | ||
|
|
||
| username_filter = ( | ||
| F(iusername__prefix=username_lower) | | ||
| F(idisplay_name__prefix=username_lower) | | ||
| F(itwitter_usernames__prefix=username_lower)) | ||
|
|
||
| users = UserMappingType.reshape( | ||
| UserMappingType | ||
| .search() | ||
| .filter(username_filter) | ||
| .values_dict('id') | ||
| [:BIG_NUMBER]) | ||
|
|
||
| return F(creator_id__in=[u['id'] for u in users]) | ||
|
|
||
| def filter_locale(self, value): | ||
| return F(locale=value) | ||
|
|
||
| def get_data(self, request): | ||
| # So filters can use the request. | ||
| self.request = request | ||
|
|
||
| # This is the base of all the metrics. Each metric branches off from | ||
| # this to get a particular metric type, since we can't do Aggregates. | ||
| query = AnswerMetricsMappingType.search() | ||
| base_filter = self.get_filters() | ||
|
|
||
| # This branch is to get the total number of answers for each user. | ||
| answer_query = ( | ||
| query | ||
| .filter(base_filter) | ||
| .facet('creator_id', filtered=True, size=BIG_NUMBER)) | ||
|
|
||
| # This branch gets the number of answers that are solutions for each user. | ||
| solutions_filter = base_filter & F(is_solution=True) | ||
| solutions_query = ( | ||
| query | ||
| .filter(solutions_filter) | ||
| .facet('creator_id', filtered=True, size=BIG_NUMBER)) | ||
|
|
||
| # This branch gets the number of helpful votes across all answers for | ||
| # each user. It is a raw facet because elasticutils only supports the | ||
| # term facet type in non-raw facets. Because it is raw facet, we have | ||
| # to also put the filter in the facet ourselves. | ||
| helpful_query = ( | ||
| query | ||
| .facet_raw( | ||
| creator_id={ | ||
| 'terms_stats': { | ||
| 'key_field': 'creator_id', | ||
| 'value_field': 'helpful_count', | ||
| }, | ||
| 'facet_filter': query._process_filters(base_filter.filters), | ||
| })) | ||
|
|
||
| # Collect three lists of objects that correlates users and the appropriate metric count | ||
| creator_answer_counts = answer_query.facet_counts()['creator_id']['terms'] | ||
| creator_solutions_counts = solutions_query.facet_counts()['creator_id']['terms'] | ||
| creator_helpful_counts = helpful_query.facet_counts()['creator_id']['terms'] | ||
|
|
||
| # Combine all the metric types into one big list. | ||
| combined = defaultdict(lambda: { | ||
| 'answer_count': 0, | ||
| 'solution_count': 0, | ||
| 'helpful_vote_count': 0, | ||
| }) | ||
|
|
||
| for d in creator_answer_counts: | ||
| combined[d['term']]['user_id'] = d['term'] | ||
| combined[d['term']]['answer_count'] = d['count'] | ||
|
|
||
| for d in creator_solutions_counts: | ||
| combined[d['term']]['user_id'] = d['term'] | ||
| combined[d['term']]['solution_count'] = d['count'] | ||
|
|
||
| for d in creator_helpful_counts: | ||
| combined[d['term']]['user_id'] = d['term'] | ||
| # Since this is a term_stats filter, not just a term filter, it is total, not count. | ||
| combined[d['term']]['helpful_vote_count'] = d['total'] | ||
|
|
||
| # Sort by answer count, and get just the ids into a list. | ||
| top_contributors = combined.values() | ||
| top_contributors.sort(key=lambda d: d['answer_count'], reverse=True) | ||
| user_ids = [c['user_id'] for c in top_contributors] | ||
| full_count = len(user_ids) | ||
|
|
||
| # Paginate those user ids. | ||
| try: | ||
| page = int(self.request.GET.get('page', 1)) | ||
| except ValueError: | ||
| page = 1 | ||
| count = 10 | ||
| page_start = (page - 1) * count | ||
| page_end = page_start + count | ||
| user_ids = user_ids[page_start:page_end] | ||
|
|
||
| # Get full user objects for every id on this page. | ||
| users = UserMappingType.reshape( | ||
| UserMappingType | ||
| .search() | ||
| .filter(id__in=user_ids) | ||
| .values_dict('id', 'username', 'display_name', 'avatar', 'last_contribution_date') | ||
| [:count]) | ||
|
|
||
| # For ever user object found, mix in the metrics counts for that user, | ||
| # and then reshape the data to make more sense to clients. | ||
| data = [] | ||
| for u in users: | ||
| d = combined[u['id']] | ||
| d['user'] = u | ||
| d['last_contribution_date'] = d['user'].get('last_contribution_date', None) | ||
| if 'user_id' in d: | ||
| del d['user_id'] | ||
| del d['user']['id'] | ||
| if 'last_contribution_date' in d['user']: | ||
| del d['user']['last_contribution_date'] | ||
| data.append(d) | ||
|
|
||
| # One last sort, since ES didn't return the users in any particular order. | ||
| data.sort(key=lambda d: d['answer_count'], reverse=True) | ||
|
|
||
| # Add ranks to the objects. | ||
| for i, contributor in enumerate(data, 1): | ||
| contributor['rank'] = page_start + i | ||
|
|
||
| return { | ||
| 'results': data, | ||
| 'count': full_count, | ||
| } |
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.
These two shouldn't be here.