зеркало из https://github.com/mozilla/kitsune.git
[bug 765816] Move Army of Awesome common replies to KB.
* Replies are now defined in KB article with slug 'army-of-awesome-common-replies'.
This commit is contained in:
Родитель
0a2cf4223a
Коммит
ced4a3141a
|
@ -0,0 +1,113 @@
|
|||
from django.conf import settings
|
||||
|
||||
from pyquery import PyQuery
|
||||
|
||||
from wiki.models import Document
|
||||
|
||||
|
||||
REPLIES_DOCUMENT_SLUG = 'army-of-awesome-common-replies'
|
||||
|
||||
|
||||
def get_common_replies(locale=settings.WIKI_DEFAULT_LANGUAGE):
|
||||
"""Returns the common replies.
|
||||
|
||||
Parses the KB article with the replies puts them in a list of dicts.
|
||||
|
||||
The KB article should have the following wiki syntax structure::
|
||||
|
||||
=Category 1=
|
||||
|
||||
==Reply 1==
|
||||
Reply goes here http://example.com/kb-article
|
||||
|
||||
==Reply 2==
|
||||
Another reply here
|
||||
|
||||
=Category 2=
|
||||
==Reply 3==
|
||||
And another reply
|
||||
|
||||
|
||||
Which results in the following HTML::
|
||||
|
||||
<h1 id="w_category-1">Category 1</h1>
|
||||
<h2 id="w_snippet-1">Reply 1</h2>
|
||||
<p>Reply goes here <a href="http://example.com/kb-article">
|
||||
http://example.com/kb-article</a>
|
||||
</p>
|
||||
<h2 id="w_snippet-2">Reply 2</h2>
|
||||
<p>Another reply here
|
||||
</p>
|
||||
<h1 id="w_category-2">Category 2</h1>
|
||||
<h2 id="w_snippet-3">Reply 3</h2>
|
||||
<p>And another reply
|
||||
</p>
|
||||
|
||||
|
||||
The resulting list returned would be::
|
||||
|
||||
[{'title': 'Category 1',
|
||||
'responses':
|
||||
[{'title': 'Reply 1',
|
||||
'response': 'Reply goes here http://example.com/kb-article'},
|
||||
{'title': 'Reply 2',
|
||||
'response': 'Another reply here'}]
|
||||
},
|
||||
{'title': 'Category 2',
|
||||
'responses':
|
||||
[{'title': 'Reply 3',
|
||||
'response': 'And another reply'}]
|
||||
}]
|
||||
|
||||
"""
|
||||
replies = []
|
||||
|
||||
# Get the replies document in the right locale, if available.
|
||||
try:
|
||||
default_doc = Document.objects.get(
|
||||
slug=REPLIES_DOCUMENT_SLUG,
|
||||
locale=settings.WIKI_DEFAULT_LANGUAGE)
|
||||
except Document.DoesNotExist:
|
||||
return replies
|
||||
|
||||
if locale != default_doc.locale:
|
||||
translated_doc = default_doc.translated_to(locale)
|
||||
doc = translated_doc or default_doc
|
||||
else:
|
||||
doc = default_doc
|
||||
|
||||
# Parse the document HTML into responses.
|
||||
pq = PyQuery(doc.html)
|
||||
|
||||
# Start at the first h1 and traverse down from there.
|
||||
try:
|
||||
current_node = pq('h1')[0]
|
||||
except IndexError:
|
||||
return replies
|
||||
|
||||
current_category = None
|
||||
current_response = None
|
||||
while current_node is not None:
|
||||
if current_node.tag == 'h1':
|
||||
# New category.
|
||||
current_category = {
|
||||
'title': current_node.text,
|
||||
'responses': []}
|
||||
replies.append(current_category)
|
||||
elif current_node.tag == 'h2':
|
||||
# New response.
|
||||
current_response = {
|
||||
'title': current_node.text,
|
||||
'response': ''}
|
||||
current_category['responses'].append(current_response)
|
||||
elif current_node.tag == 'p':
|
||||
# The text for a response.
|
||||
text = current_node.text_content().strip()
|
||||
if text and current_response:
|
||||
current_response['response'] = text
|
||||
|
||||
# Ignore any other tags that come through.
|
||||
|
||||
current_node = current_node.getnext()
|
||||
|
||||
return replies
|
|
@ -6,32 +6,92 @@ from django.core.cache import cache
|
|||
from nose.tools import eq_
|
||||
from pyquery import PyQuery as pq
|
||||
|
||||
from customercare.replies import REPLIES_DOCUMENT_SLUG
|
||||
from sumo.urlresolvers import reverse
|
||||
from sumo.tests import TestCase
|
||||
from wiki.tests import document, revision
|
||||
|
||||
|
||||
CANNED_RESPONSES_WIKI = """
|
||||
Any initial text above the first H1 should be ignored.
|
||||
|
||||
=Category 1=
|
||||
|
||||
==Reply 1==
|
||||
Reply goes here http://example.com/kb-article
|
||||
|
||||
==Reply 2==
|
||||
Another reply here
|
||||
|
||||
=Category 2=
|
||||
==Reply 3==
|
||||
And another reply
|
||||
"""
|
||||
|
||||
MESSED_UP_CANNED_RESPONSES_WIKI = """
|
||||
Lal al ala la alaa lala la
|
||||
==Bogus Reply will be ignored==
|
||||
==Another bogus one==
|
||||
Any initial text above the first H1 should be ignored.
|
||||
|
||||
=Category 1=
|
||||
|
||||
==Reply 1==
|
||||
Reply goes here http://example.com/kb-article
|
||||
|
||||
==Reply 2==
|
||||
Another reply here [[Bad link]]
|
||||
|
||||
==A reply without text==
|
||||
=Category 2=
|
||||
==Another reply without text==
|
||||
==Reply 3==
|
||||
And another reply
|
||||
==Another Reply without text==
|
||||
"""
|
||||
|
||||
|
||||
class CannedResponsesTestCase(TestCase):
|
||||
"""Canned responses tests."""
|
||||
|
||||
def _create_doc(self, content):
|
||||
# Create the canned responses article.
|
||||
doc = document(slug=REPLIES_DOCUMENT_SLUG, save=True)
|
||||
rev = revision(
|
||||
document=doc,
|
||||
content=content,
|
||||
is_approved=True,
|
||||
save=True)
|
||||
|
||||
doc.current_revision = rev
|
||||
doc.save()
|
||||
|
||||
def test_list_canned_responses(self):
|
||||
"""Listing canned responses works as expected."""
|
||||
|
||||
# Create the canned responses article.
|
||||
self._create_doc(CANNED_RESPONSES_WIKI)
|
||||
|
||||
r = self.client.get(reverse('customercare.landing'), follow=True)
|
||||
eq_(200, r.status_code)
|
||||
doc = pq(r.content)
|
||||
responses_plain = doc('#accordion').text()
|
||||
|
||||
# Listing all categories
|
||||
assert "Welcome and Thanks" in responses_plain
|
||||
assert "Using Firefox" in responses_plain
|
||||
assert "Support" in responses_plain
|
||||
assert "Get Involved" in responses_plain
|
||||
# Verify categories and replies
|
||||
assert 'Category 1' in responses_plain
|
||||
assert 'Reply 1' in responses_plain
|
||||
assert 'Reply goes here' in responses_plain
|
||||
assert 'Category 2' in responses_plain
|
||||
assert 'Reply 3' in responses_plain
|
||||
assert 'And another reply' in responses_plain
|
||||
|
||||
# Listing all responses
|
||||
eq_(22, len(doc('#accordion a.reply-topic')))
|
||||
eq_(3, len(doc('#accordion a.reply-topic')))
|
||||
|
||||
def test_list_canned_responses_nondefault_locale(self):
|
||||
"""Listing canned responses gives all snippets regardless of locale."""
|
||||
# Create the canned responses article.
|
||||
self._create_doc(CANNED_RESPONSES_WIKI)
|
||||
|
||||
r = self.client.get(reverse('customercare.landing', locale='es'),
|
||||
follow=True)
|
||||
|
@ -39,7 +99,20 @@ class CannedResponsesTestCase(TestCase):
|
|||
doc = pq(r.content)
|
||||
|
||||
# Listing all responses, l10n-agnostic (English if not in Verbatim).
|
||||
eq_(22, len(doc('#accordion a.reply-topic')))
|
||||
eq_(3, len(doc('#accordion a.reply-topic')))
|
||||
|
||||
def test_messed_up_canned_responses(self):
|
||||
"""Make sure we don't blow up if the article is malformed."""
|
||||
# Create the canned responses article.
|
||||
self._create_doc(MESSED_UP_CANNED_RESPONSES_WIKI)
|
||||
|
||||
r = self.client.get(reverse('customercare.landing'), follow=True)
|
||||
eq_(200, r.status_code)
|
||||
doc = pq(r.content)
|
||||
responses_plain = doc('#accordion').text()
|
||||
|
||||
assert 'Category 1' in responses_plain
|
||||
assert 'Category 2' in responses_plain
|
||||
|
||||
|
||||
class TweetListTestCase(TestCase):
|
||||
|
|
|
@ -19,6 +19,7 @@ from tower import ugettext as _, ugettext_lazy as _lazy
|
|||
import tweepy
|
||||
|
||||
from customercare.models import Tweet, Reply
|
||||
from customercare.replies import get_common_replies
|
||||
from sumo.redis_utils import redis_client, RedisError
|
||||
import twitter
|
||||
|
||||
|
@ -32,148 +33,6 @@ FILTERS = SortedDict([('recent', _lazy('Most Recent')),
|
|||
('all', _lazy('All'))])
|
||||
|
||||
|
||||
CANNED_RESPONSES = [
|
||||
{'title': _lazy("Welcome and Thanks"),
|
||||
'responses':
|
||||
[{'title': _lazy("Thanks for using Firefox"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("thanks for using Firefox! You're not just a user, "
|
||||
"but part of a community that's 400M strong "
|
||||
"http://mzl.la/e8xdv5")
|
||||
},
|
||||
{'title': _lazy("Tips & tricks"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("getting started with Firefox? Here are some tips "
|
||||
"& tricks for getting the most out of it "
|
||||
"http://mzl.la/c0B9P2")
|
||||
},
|
||||
{'title': _lazy("We're a non-profit organization"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("hey, I'm a Mozilla volunteer. Did you know there "
|
||||
"are 1000s of us worldwide? More here "
|
||||
"http://mzl.la/cvlwvd")
|
||||
},
|
||||
{'title': _lazy("Welcome to our community"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("Thanx for joining Moz! You're now part of our "
|
||||
"global community. We're here if you need help "
|
||||
"http://mzl.la/bMDof6")
|
||||
}]
|
||||
},
|
||||
{'title': _lazy("Using Firefox"),
|
||||
'responses':
|
||||
[{'title': _lazy("Add-on reviews"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("getting started with Firefox? Add-ons personalize it"
|
||||
" w cool features & function. Some faves "
|
||||
"http://mzl.la/cGypVI")
|
||||
},
|
||||
{'title': _lazy("Customize Firefox with add-ons"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("have you tried add-ons? Cool apps for shopping, "
|
||||
"music, news, whatever you do online. Start here: "
|
||||
"http://mzl.la/blOuoD")
|
||||
},
|
||||
{'title': _lazy("Firefox Panorama"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("heard about Firefox Panorama? It groups + displays"
|
||||
" your tabs, eliminating clutter. Try it "
|
||||
"http://mzl.la/d21MyY")
|
||||
},
|
||||
{'title': _lazy("Firefox Sync"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("tried Firefox Sync? It's awesome! Switch computers"
|
||||
" & it saves open tabs, pwords, history. Try it"
|
||||
" http://mzl.la/aHHUYA")
|
||||
},
|
||||
{'title': _lazy("Update plugins and add-ons"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("have you updated your plug-ins and add-ons? Should"
|
||||
" work out the kinks. Here's the place to refresh "
|
||||
"http://mzl.la/cGCg12")
|
||||
},
|
||||
{'title': _lazy("Upgrade Firefox"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("hey, maybe you need to upgrade Firefox? New "
|
||||
"version is speedier with a lot more going on: "
|
||||
"http://mzl.la/9wJe30")
|
||||
}]
|
||||
},
|
||||
{'title': _lazy("Support"),
|
||||
'responses':
|
||||
[{'title': _lazy("Ask SUMO"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("maybe ask SUMO about this issue? Firefox's community"
|
||||
" support team. They'll know what's up: "
|
||||
"http://mzl.la/bMDof6")
|
||||
},
|
||||
{'title': _lazy("Firefox doesn't behave"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("sorry your Firefox doesn't behave. Check out the "
|
||||
"tips here http://mzl.la/bNps7F")
|
||||
},
|
||||
{'title': _lazy("Firefox is slow"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("you can make your Firefox fast again. Try out "
|
||||
"these steps http://mzl.la/9bB1FY")
|
||||
},
|
||||
{'title': _lazy("Fix crashes"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("sorry your Firefox is hanging :( Here are quick "
|
||||
"fixes to prevent this again http://mzl.la/atSsFt")
|
||||
},
|
||||
{'title': _lazy("High RAM usage"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("Firefox sometimes uses more memory than it should."
|
||||
" Try one of these easy fixes http://mzl.la/fPTNo8")
|
||||
},
|
||||
{'title': _lazy("Quick Firefox fixes"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("have you tried Firefox support? If their quick "
|
||||
"fixes don't help, volunteers can assist! "
|
||||
"http://mzl.la/9V9uWd")
|
||||
},
|
||||
{'title': _lazy("Slow Firefox startup"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("Firefox needs a refresh. Here are tips to make "
|
||||
"Firefox load faster http://mzl.la/r0mGyN")
|
||||
}]
|
||||
},
|
||||
{'title': _lazy("Get Involved"),
|
||||
'responses':
|
||||
[{'title': _lazy("Become a beta tester"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("become a beta tester! Help develop the next Firefox."
|
||||
"You don't have to be a techie to contribute: "
|
||||
"http://mzl.la/d23n7a")
|
||||
},
|
||||
{'title': _lazy("Get involved with Mozilla"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("Want a better web? Join the Mozilla movement. "
|
||||
"There is something to do for everyone. Get started"
|
||||
" http://mzl.la/cufJmX")
|
||||
},
|
||||
{'title': _lazy("Join Drumbeat"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("want to spark a movement? Mozilla Drumbeat is your"
|
||||
" chance to keep the web open and free. More info "
|
||||
"http://mzl.la/aIXCLA")
|
||||
},
|
||||
{'title': _lazy("Mozilla Developer Network"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("help make the web better! Build web pages, apps "
|
||||
"& add-ons here: Mozilla Developer Network "
|
||||
"http://mzl.la/9gQfrn")
|
||||
},
|
||||
{'title': _lazy("Report a bug"),
|
||||
# L10n: This is a reply tweet, so it must fit in 140 characters.
|
||||
'response': _lazy("Thanks for finding a bug. Make everyone's Firefox"
|
||||
" experience better by reporting. It's easy "
|
||||
"http://mzl.la/bcujVc")
|
||||
}]
|
||||
}]
|
||||
|
||||
|
||||
def _tweet_for_template(tweet, https=False):
|
||||
"""Return the dict needed for tweets.html to render a tweet + replies."""
|
||||
data = json.loads(tweet.raw_json)
|
||||
|
@ -313,7 +172,7 @@ def landing(request):
|
|||
return jingo.render(request, 'customercare/landing.html', {
|
||||
'activity_stats': activity_stats,
|
||||
'contributor_stats': contributor_stats,
|
||||
'canned_responses': CANNED_RESPONSES,
|
||||
'canned_responses': get_common_replies(request.locale),
|
||||
'tweets': _get_tweets(locale=request.locale,
|
||||
https=request.is_secure()),
|
||||
'authed': twitter.authed,
|
||||
|
|
|
@ -27,7 +27,7 @@ Update settings_local.py
|
|||
Set the following settings in settings_local.py::
|
||||
|
||||
TWITTER_CONSUMER_KEY = <consumer key>
|
||||
TWITTER_CONSUMER_SECRET = <consumer secret>
|
||||
TWITTER_CONSUMER_SECRET = <consumer secret>
|
||||
TWITTER_COOKIE_SECURE = False
|
||||
|
||||
|
||||
|
@ -40,3 +40,28 @@ To fetch tweets, run::
|
|||
|
||||
|
||||
You should now see tweets at /army-of-awesome.
|
||||
|
||||
|
||||
Common replies
|
||||
==============
|
||||
|
||||
Common replies should be defined in a wiki article with slug
|
||||
'army-of-awesome-common-replies'. The format for the article
|
||||
content is::
|
||||
|
||||
=Category 1=
|
||||
|
||||
==Reply 1==
|
||||
Reply goes here http://example.com/kb-article
|
||||
|
||||
==Reply 2==
|
||||
Another reply here
|
||||
|
||||
=Category 2=
|
||||
==Reply 3==
|
||||
And another reply
|
||||
|
||||
Note that replies can't be over 140 characters long, including the
|
||||
@username and #fxhelp hash tag. Therefore, each reply defined here
|
||||
should not go over 125 characters. The reply must be plain text
|
||||
without any HTML or wiki syntax.
|
||||
|
|
Загрузка…
Ссылка в новой задаче