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 re
import rfc822 import rfc822
import urllib import urllib
import urllib2
from django.conf import settings from django.conf import settings
from django.core.cache import cache
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from django.utils.encoding import smart_str from django.utils.encoding import smart_str
import cronjobs import cronjobs
import tweepy
from .models import Tweet from customercare.models import Tweet
SEARCH_URL = 'http://search.twitter.com/search.json' SEARCH_URL = 'http://search.twitter.com/search.json'
@ -118,3 +121,56 @@ def _filter_tweet(item):
return None return None
return item 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> <li class="respond">Respond to the tweet!</li>
</ol> </ol>
</div> </div>
<div id="tweetcontainer"> <div id="tweetcontainer">
<div class="tweets-header"> <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> <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> </form>
{% endif %} {% endif %}
</div> </div>
<br style="clear:both; height: 1px" /> <br style="clear:both; height: 1px" />
<ul id="tweets"> <ul id="tweets">
@ -49,6 +49,61 @@
{% endblock %} {% endblock %}
{% block extra_sidebar %} {% 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"> <div id="side-getinvolved">
<h3>Take it to the next level!</h3> <h3>Take it to the next level!</h3>
<p>Want to go beyond 140 characters? Join the support community and help many more <p>Want to go beyond 140 characters? Join the support community and help many more

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

@ -5,9 +5,11 @@ import logging
import time import time
from django.conf import settings from django.conf import settings
from django.core.cache import cache
from django.http import HttpResponse, HttpResponseBadRequest from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.http import require_POST, require_GET from django.views.decorators.http import require_POST, require_GET
from babel.numbers import format_number
from bleach import Bleach from bleach import Bleach
import jingo import jingo
import tweepy import tweepy
@ -79,7 +81,40 @@ def landing(request):
canned_responses = CannedCategory.objects.all() 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', { return jingo.render(request, 'customercare/landing.html', {
'activity_stats': activity_stats,
'contributor_stats': contributor_stats,
'canned_responses': canned_responses, 'canned_responses': canned_responses,
'tweets': _get_tweets(), 'tweets': _get_tweets(),
'authed': twitter.authed, 'authed': twitter.authed,

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

@ -67,13 +67,89 @@
text-decoration: underline; text-decoration: underline;
} }
#side-getinvolved { #side-stats {
background: transparent url('../img/side-getinvolved-bg.png') no-repeat top left; margin-top: 40px;
margin-top: 140px; }
padding-top: 20px; #side-stats .unavailable {
color: #999186; 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 { #side-getinvolved h3 {
color: #1E4262; color: #1E4262;
font-size: 150%; font-size: 150%;

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

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

После

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

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

@ -340,5 +340,25 @@
} }
); );
}).bullseye(); }).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)); }(jQuery));

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

@ -535,16 +535,21 @@ THUMBNAIL_PROGRESS_WIDTH = 32 # width of the above image
THUMBNAIL_PROGRESS_HEIGHT = 32 # height of the above image THUMBNAIL_PROGRESS_HEIGHT = 32 # height of the above image
VIDEO_MAX_FILESIZE = 16777216 # 16 megabytes, in bytes VIDEO_MAX_FILESIZE = 16777216 # 16 megabytes, in bytes
# Customer Care tweet collection settings # Customer Care settings
CC_MAX_TWEETS = 500 # Max. no. of tweets in DB 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_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_TWEET_ACTIVITY_URL = 'https://metrics.mozilla.com/stats/twitter/armyOfAwesomeKillRate.json' # Tweet activity stats
CC_SHOW_REPLIES = True 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_KEY = ''
TWITTER_CONSUMER_SECRET = '' TWITTER_CONSUMER_SECRET = ''
NOTIFICATIONS_FROM_ADDRESS = 'notifications@support.mozilla.com' NOTIFICATIONS_FROM_ADDRESS = 'notifications@support.mozilla.com'
# URL of the chat server. # URL of the chat server.