Merge branch 'cc-tweet-summary-612952'

This commit is contained in:
Fred Wenzel 2010-12-01 16:36:15 -08:00
Родитель a523e8084c a6f1910595
Коммит 9effabdf32
7 изменённых файлов: 260 добавлений и 13 удалений

Просмотреть файл

@ -5,14 +5,17 @@ import logging
import re
import rfc822
import urllib
import urllib2
from django.conf import settings
from django.core.cache import cache
from django.db.utils import IntegrityError
from django.utils.encoding import smart_str
import cronjobs
import tweepy
from .models import Tweet
from customercare.models import Tweet
SEARCH_URL = 'http://search.twitter.com/search.json'
@ -118,3 +121,56 @@ def _filter_tweet(item):
return None
return item
@cronjobs.register
def get_customercare_stats():
"""
Fetch Customer Care stats from Mozilla Metrics.
Example Activity Stats data:
{"resultset": [["Yesterday",1234,123,0.0154],
["Last Week",12345,1234,0.0240], ...]
"metadata": [...]}
Example Top Contributor data:
{"resultset": [[1,"Overall","John Doe","johndoe",840],
[2,"Overall","Jane Doe","janedoe",435], ...],
"metadata": [...]}
"""
stats_sources = {
settings.CC_TWEET_ACTIVITY_URL: settings.CC_TWEET_ACTIVITY_CACHE_KEY,
settings.CC_TOP_CONTRIB_URL: settings.CC_TOP_CONTRIB_CACHE_KEY,
}
for url, cache_key in stats_sources.items():
log.debug('Updating %s from %s' % (cache_key, url))
try:
json_data = json.load(urllib2.urlopen(url))
json_data['resultset'] = ''
if not json_data['resultset']:
raise KeyError('Result set was empty.')
except Exception, e:
log.error('Error updating %s: %s' % (cache_key, e))
continue
# Grab top contributors' avatar URLs from the public twitter API.
if cache_key == settings.CC_TOP_CONTRIB_CACHE_KEY:
twitter = tweepy.API()
avatars = {}
for contrib in json_data['resultset']:
username = contrib[3]
if avatars.get(username):
continue
try:
user = twitter.get_user(username)
except tweepy.TweepError, e:
log.warning('Error grabbing avatar of user %s: %s' % (
username, e))
else:
avatars[username] = user.profile_image_url
json_data['avatars'] = avatars
cache.set(cache_key, json_data, settings.CC_STATS_CACHE_TIMEOUT)

Просмотреть файл

@ -18,7 +18,7 @@
<li class="respond">Respond to the tweet!</li>
</ol>
</div>
<div id="tweetcontainer">
<div class="tweets-header">
<img id="twitter-icon" src="{{ MEDIA_URL }}img/customercare/twitter-icon.png" /><h2 class="showhide_heading" id="Where_to_ask_your_question">Choose a tweet to help</h2>
@ -31,7 +31,7 @@
</form>
{% endif %}
</div>
<br style="clear:both; height: 1px" />
<ul id="tweets">
@ -49,6 +49,61 @@
{% endblock %}
{% block extra_sidebar %}
<div id="side-stats">
<h3>Our army has responded to:</h3>
{% if not activity_stats %}
<p class="unavailable">Recent stats not available.</p>
{% else %}
<div class="bubble">
<div class="perc">
<span class="data">{{ activity_stats[0][1]['perc'] }}</span>%
<span class="label">of tweets</span>
</div>
<div class="numbers">
<div class="replies">
<span class="data">{{ activity_stats[0][1]['replies'] }}</span>
<span class="label">replies</span>
</div>
/
<div class="tweets">
<span class="data">{{ activity_stats[0][1]['requests'] }}</span>
<span class="label">tweets</span>
</div>
</div>
<select>
{% for act in activity_stats %}
<option value="{{ loop.index0 }}"
data-perc ="{{ act[1]['perc'] }}"
data-replies="{{ act[1]['replies'] }}"
data-requests="{{ act[1]['requests'] }}">
{{ act[0] }}
</option>
{% endfor %}
</select>
</div>
<div class="speech"></div>
{% endif %}
<div class="contribs">
{% if contributor_stats %}
{% for act in activity_stats %}
{% set period = act[0] %}
<div class="contributors period{{ loop.index0 }}"
data-period="{{ period }}">
{% for contrib in contributor_stats.get(period, []) %}
<a href="http://twitter.com/{{ contrib['username'] }}" target="_blank">
<img src="{{ contrib['avatar'] }}" alt="{{ contrib['username'] }}"
title="{{ contrib['name']}}: {{ contrib['count'] }} replies"
class="avatar" />
</a>
{% endfor %}
</div>
{% endfor %}
{% endif %}
</div>
</div>
<div id="side-getinvolved">
<h3>Take it to the next level!</h3>
<p>Want to go beyond 140 characters? Join the support community and help many more

Просмотреть файл

@ -5,9 +5,11 @@ import logging
import time
from django.conf import settings
from django.core.cache import cache
from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.http import require_POST, require_GET
from babel.numbers import format_number
from bleach import Bleach
import jingo
import tweepy
@ -79,7 +81,40 @@ def landing(request):
canned_responses = CannedCategory.objects.all()
# Stats. See customercare.cron.get_customercare_stats.
activity = cache.get(settings.CC_TWEET_ACTIVITY_CACHE_KEY)
if activity:
activity_stats = []
for act in activity['resultset']:
activity_stats.append((act[0], {
'requests': format_number(act[1], locale='en_US'),
'replies': format_number(act[2], locale='en_US'),
'perc': int(round(act[3] * 100)),
}))
else:
activity_stats = None
contributors = cache.get(settings.CC_TOP_CONTRIB_CACHE_KEY)
if contributors:
contributor_stats = {}
for contrib in contributors['resultset']:
# Create one list per time period
period = contrib[1]
if not contributor_stats.get(period):
contributor_stats[period] = []
contributor_stats[period].append({
'name': contrib[2],
'username': contrib[3],
'count': contrib[4],
'avatar': contributors['avatars'].get(contrib[3]),
})
else:
contributor_stats = None
return jingo.render(request, 'customercare/landing.html', {
'activity_stats': activity_stats,
'contributor_stats': contributor_stats,
'canned_responses': canned_responses,
'tweets': _get_tweets(),
'authed': twitter.authed,

Просмотреть файл

@ -67,13 +67,89 @@
text-decoration: underline;
}
#side-getinvolved {
background: transparent url('../img/side-getinvolved-bg.png') no-repeat top left;
margin-top: 140px;
padding-top: 20px;
color: #999186;
#side-stats {
margin-top: 40px;
}
#side-stats .unavailable {
font-size: 1.2em;
font-style: italic;
margin-top: 10px;
}
#side-stats .bubble {
background-color: #E6F0F9;
-webkit-border-radius: 50px;
-moz-border-radius: 50px;
border-radius: 50px;
margin-top: 10px;
text-align: center;
}
#side-stats .speech {
background: transparent url(../img/customercare/bubble.png) no-repeat 50px bottom;
height: 20px;
width: 100%;
}
#side-stats .label {
color: #00639B;
font-family: Arial,Helvetica,sans-serif;
font-size: .75em;
font-style: normal;
font-weight: bold;
font-variant: small-caps;
}
#side-stats .perc {
font-size: 2.5em;
font-style: italic;
padding-top: 10px;
text-align: center;
}
#side-stats .numbers {
font-size: 1.8em;
}
#side-stats .numbers .replies,
#side-stats .numbers .tweets {
text-align: center;
width: 45%;
}
#side-stats .numbers .replies {
float: left;
}
#side-stats .numbers .tweets {
float: right;
}
#side-stats .numbers .data {
display: block;
margin-bottom: -.3em;
}
#side-stats select {
font-family: Arial,Helvetica,sans-serif;
margin: 10px 0 20px;
text-align: center;
width: 80%;
}
#side-stats .contribs {
clear: both;
margin: 10px 20px 0;
width: 160px;
}
#side-stats .avatar {
margin: 2px;
width: 32px;
}
#side-stats .contribs .contributors {
display: none;
}
#side-stats .contribs .contributors:first-child {
display: block;
}
#side-getinvolved {
background: transparent url('../img/side-getinvolved-bg.png') no-repeat top left;
padding-top: 20px;
color: #999186;
margin-top: 40px;
}
#side-stats h3,
#side-getinvolved h3 {
color: #1E4262;
font-size: 150%;

Двоичные данные
media/img/customercare/bubble.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.1 KiB

Просмотреть файл

@ -340,5 +340,25 @@
}
);
}).bullseye();
/* Statistics */
$('#side-stats select').change(function(e) {
var $this = $(this),
option = $this.children('option[value=' + $this.val() + ']'),
bubble = $('#side-stats .bubble')
contribs = $('#side-stats .contribs');
// Update numbers
bubble.find('.perc .data').text(option.attr('data-perc'));
bubble.find('.replies .data').text(option.attr('data-replies'));
bubble.find('.tweets .data').text(option.attr('data-requests'));
// Update contributors
contribs.find('.contributors:visible').fadeOut('fast', function() {
contribs.find('.contributors.period' + $this.val()).fadeIn('fast');
});
$this.blur();
e.preventDefault();
}).val('0');
});
}(jQuery));

Просмотреть файл

@ -535,16 +535,21 @@ THUMBNAIL_PROGRESS_WIDTH = 32 # width of the above image
THUMBNAIL_PROGRESS_HEIGHT = 32 # height of the above image
VIDEO_MAX_FILESIZE = 16777216 # 16 megabytes, in bytes
# Customer Care tweet collection settings
CC_MAX_TWEETS = 500 # Max. no. of tweets in DB
CC_TWEETS_PERPAGE = 100 # How many tweets to collect in one go. Max: 100.
# Customer Care settings
CC_MAX_TWEETS = 500 # Max. no. of tweets in DB
CC_TWEETS_PERPAGE = 100 # How many tweets to collect in one go. Max: 100.
CC_SHOW_REPLIES = True # Show replies to tweets?
# Show replies to tweets?
CC_SHOW_REPLIES = True
CC_TWEET_ACTIVITY_URL = 'https://metrics.mozilla.com/stats/twitter/armyOfAwesomeKillRate.json' # Tweet activity stats
CC_TOP_CONTRIB_URL = 'https://metrics.mozilla.com/stats/twitter/armyOfAwesomeTopSoldiers.json' # Top contributor stats
CC_TWEET_ACTIVITY_CACHE_KEY = 'sumo-cc-tweet-stats'
CC_TOP_CONTRIB_CACHE_KEY = 'sumo-cc-top-contrib-stats'
CC_STATS_CACHE_TIMEOUT = 24 * 60 * 60 # 24 hours
TWITTER_CONSUMER_KEY = ''
TWITTER_CONSUMER_SECRET = ''
NOTIFICATIONS_FROM_ADDRESS = 'notifications@support.mozilla.com'
# URL of the chat server.