зеркало из https://github.com/mozilla/kitsune.git
Create a new chat app. [bug 605476]
Created a new app because it involves multiple views and a cron. The cron will need to be set up to run once/minute. It does a proxy request to the chat server (settings.CHAT_SERVER) to get the queue status, then stores the result in the cache (settings.CHAT_CACHE_KEY). The /chat/queue-status/ view dumps out whatever's in the cache or returns a 503 status if the cache is empty.
This commit is contained in:
Родитель
16491dc5d4
Коммит
ce54806721
|
@ -0,0 +1,16 @@
|
|||
import urllib2
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
|
||||
import cronjobs
|
||||
|
||||
|
||||
@cronjobs.register
|
||||
def get_queue_status():
|
||||
"""Update the live chat queue status."""
|
||||
|
||||
status_path = '/plugins/fastpath/workgroup-stats?workgroup=support&rand='
|
||||
url = settings.CHAT_SERVER + status_path
|
||||
xml = urllib2.urlopen(url)
|
||||
cache.set(settings.CHAT_CACHE_KEY, xml.read())
|
|
@ -0,0 +1,66 @@
|
|||
{# vim: set ts=2 et sts=2 sw=2: #}
|
||||
{% extends 'wiki/base.html' %}
|
||||
{% from "wiki/includes/sidebar_modules.html" import quick_links %}
|
||||
{% set title = _('Live Chat') %}
|
||||
{% set scripts = ('chat',) %}
|
||||
{% set styles = ('wiki', 'chat') %}
|
||||
|
||||
{% block content %}
|
||||
<article id="chat" class="main">
|
||||
<hgroup>
|
||||
<h1>{{ _('Live Chat') }}</h1>
|
||||
<h2>{% trans %}Live Chat provides real time text support right in your browser.
|
||||
If Foxkeh says we're open, click on him to begin a one-on-one chat with one
|
||||
of our volunteers.{% endtrans %}</h2>
|
||||
</hgroup>
|
||||
<section id="chat-status" data-server="{{ settings.CHAT_SERVER }}">
|
||||
<h1>{{ _('Checking Live Chat Status...') }}</h1>
|
||||
</section>
|
||||
<section>
|
||||
<h1 id="How_to_get_started">{{ _('How to get started') }}</h1>
|
||||
<ol>
|
||||
<li>{% trans %}If this page says "We're Open," that means people are ready to help
|
||||
you with your Firefox problem. Click on the fox to launch the chat
|
||||
window.{% endtrans %}</li>
|
||||
<li>{% trans %}Fill out as many of the fields as you can and click <span
|
||||
class='button'>Start Chat</span>.{% endtrans %}</li>
|
||||
<li>{% trans %}When someone answers your question, the request window will change
|
||||
to show a chat field and the conversation starts.{% endtrans %}</li>
|
||||
</ol>
|
||||
|
||||
<h1 id="What_you_should_know">{{ _('What you should know') }}</h1>
|
||||
|
||||
<ul>
|
||||
<li>{% trans %}Helpers are volunteers, not Mozilla employees.{% endtrans %}</li>
|
||||
<li>{% trans %}We're currently only providing support in English.{% endtrans %}</li>
|
||||
<li>{% trans report='mailto:abuse@support.mozilla.com' %}We'll never ask you to give us
|
||||
personal information, including passwords. If someone does,
|
||||
<a href="{{ report }}">report them</a>.{% endtrans %}</li>
|
||||
<li>{% trans rules=url('wiki.document', 'Forum and chat rules and guidelines') %}We expect our users
|
||||
and helpers to be polite with each other; <a href="{{ rules }}">see our rules</a>.{% endtrans %}</li>
|
||||
<li>{% trans %}Transcripts are kept for quality purposes and to keep info in the
|
||||
knowledge base up to date. You'll be given a chance to request a copy
|
||||
of the transcript when your chat is over.{% endtrans %}</li>
|
||||
<li>{% trans %}Right now we only help with issues using Firefox. If you want chat
|
||||
support for other products, or for writing add-ons or contributing code,
|
||||
please go to <a href="http://irc.mozilla.org">Mozilla's
|
||||
IRC network</a>.{% endtrans %}</li>
|
||||
</ul>
|
||||
|
||||
<p><a href="{{ url('wiki.document', 'Terms of service') }}">{{ _('Terms of service') }}</a>
|
||||
| <a href="http://www.mozilla.com/privacy-policy.html">{{ _('Privacy Policy') }}</a></p>
|
||||
|
||||
<h1 id="Want_to_help">{{ _('Want to help?') }}</h1>
|
||||
|
||||
<p>{% trans help=url('wiki.document', 'Helping with Live Chat') %}Our Live Chat volunteers are regular Firefox users like you!
|
||||
Check out what you need to know to
|
||||
<a href="{{ help }}">help users through Live Chat</a>.{% endtrans %}</p>
|
||||
</section>
|
||||
</article>
|
||||
{% endblock %}
|
||||
|
||||
{% block side %}
|
||||
<section>
|
||||
{{ quick_links() }}
|
||||
</section>
|
||||
{% endblock %}
|
|
@ -0,0 +1,22 @@
|
|||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
|
||||
from nose.tools import eq_
|
||||
|
||||
from sumo.tests import TestCase
|
||||
from sumo.urlresolvers import reverse
|
||||
|
||||
|
||||
class ChatTestCase(TestCase):
|
||||
def test_uncached(self):
|
||||
cache.delete(settings.CHAT_CACHE_KEY)
|
||||
resp = self.client.get(reverse('chat.queue-status', locale='en-US'))
|
||||
eq_(503, resp.status_code)
|
||||
eq_('', resp.content)
|
||||
|
||||
def test_cached(self):
|
||||
source = 'The Output'
|
||||
cache.set(settings.CHAT_CACHE_KEY, source)
|
||||
resp = self.client.get(reverse('chat.queue-status', locale='en-US'))
|
||||
eq_(200, resp.status_code)
|
||||
eq_(source, resp.content)
|
|
@ -0,0 +1,6 @@
|
|||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
urlpatterns = patterns('chat.views',
|
||||
url(r'^/$', 'chat', name='chat.home'),
|
||||
url(r'^/queue-status/$', 'queue_status', name='chat.queue-status'),
|
||||
)
|
|
@ -0,0 +1,30 @@
|
|||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.http import HttpResponse
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.views.decorators.http import require_GET
|
||||
|
||||
import jingo
|
||||
|
||||
|
||||
@require_GET
|
||||
def chat(request):
|
||||
"""Display the current state of the chat queue."""
|
||||
return jingo.render(request, 'chat/chat.html')
|
||||
|
||||
|
||||
@never_cache
|
||||
@require_GET
|
||||
def queue_status(request):
|
||||
"""Dump the queue status out of the cache.
|
||||
|
||||
See chat.crons.get_queue_status.
|
||||
|
||||
"""
|
||||
|
||||
xml = cache.get(settings.CHAT_CACHE_KEY)
|
||||
status = 200
|
||||
if not xml:
|
||||
xml = ''
|
||||
status = 503
|
||||
return HttpResponse(xml, mimetype='application/xml', status=status)
|
|
@ -6,7 +6,6 @@ urlpatterns = patterns('dashboards.views',
|
|||
url(r'^$', redirect_to, {'url': 'home'}),
|
||||
url(r'^home/$', 'home', name='home'),
|
||||
# TODO: mobile home page
|
||||
# TODO: live chat page
|
||||
url(r'^contributors$', 'contributors', name='dashboards.contributors'),
|
||||
# TODO: more contributor dashboard
|
||||
url(r'^localization$', 'localization', name='dashboards.localization'),
|
||||
|
|
|
@ -14,6 +14,7 @@ from sumo.urlresolvers import reverse
|
|||
from wiki.models import Document
|
||||
from wiki.views import SHOWFOR_DATA
|
||||
|
||||
|
||||
HOME_DOCS = {'quick': 'Home page - Quick', 'explore': 'Home page - Explore'}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
article#chat h2 {
|
||||
color: #4B4742;
|
||||
font-size: 18px;
|
||||
line-height: 150%;
|
||||
}
|
||||
|
||||
article#chat section {
|
||||
clear: left;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
article#chat section h1 {
|
||||
margin: 18px 0 12px;
|
||||
}
|
||||
|
||||
article#chat ol,
|
||||
article#chat ul {
|
||||
line-height: 150%;
|
||||
margin: 0 0 6px 21px;
|
||||
}
|
||||
|
||||
#chat-status {
|
||||
padding-bottom: 120px;
|
||||
padding-top: 70px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#chat-status div {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#chat-status img {
|
||||
float: left;
|
||||
margin-top: -60px;
|
||||
padding-left: 30px;
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
#chat-status ul {
|
||||
line-height: 170%;
|
||||
margin-top: 9px;
|
||||
}
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 12 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 17 KiB |
|
@ -0,0 +1,108 @@
|
|||
(function() {
|
||||
$(document).ready(function() {
|
||||
var $status = $('#chat-status'),
|
||||
server = $status.attr('data-server');
|
||||
|
||||
var statusUrl = 'queue-status/',
|
||||
startUrl = '/webchat/start.jsp?workgroup=support@workgroup.chat-support.mozilla.com&location=http://bk-sumo.khan.mozilla.org/en-US/kb/Live+chat',
|
||||
openImage = '/media/img/chat/foxkeh-open.png',
|
||||
closedImage = '/media/img/chat/foxkeh-closed.png';
|
||||
|
||||
var $header = $('<h1>'),
|
||||
img = new Image(),
|
||||
$infoList = $('<ul id="chat-queue-info">'),
|
||||
$container = $('<div>');
|
||||
img.height = img.width = 200;
|
||||
|
||||
function checkQueueStatus() {
|
||||
|
||||
$.ajax({
|
||||
url: statusUrl,
|
||||
success: function updateQueueStatus (data, textStatus, xhr) {
|
||||
$status.html('');
|
||||
$infoList.html('');
|
||||
$container.html('');
|
||||
// populated the $status element.
|
||||
var queueStatus = {};
|
||||
$(data).find('stat').each(function() {
|
||||
var k = $(this).attr('name'),
|
||||
v = $(this).text();
|
||||
queueStatus[k] = v;
|
||||
});
|
||||
|
||||
switch(queueStatus['status']) {
|
||||
case 'OPEN':
|
||||
// queue is open and accepting new chats.
|
||||
$header.text(gettext("We're open!"));
|
||||
img.src = openImage;
|
||||
img.alt = gettext('Our volunteers are ready to help.');
|
||||
var $online = $('<li>').text(gettext('Helpers online: ') + queueStatus['active-agents']),
|
||||
$inQueue = $('<li>').text(gettext('Users waiting: ') + queueStatus['requests-waiting']),
|
||||
$waitTime = $('<li>').text(gettext('Estimated wait: ') + getTimeDisplay(queueStatus['longest-wait'])),
|
||||
$start = $('<a>').attr('href', server+startUrl);
|
||||
|
||||
$start.click(function(evt) {
|
||||
window.open($start.attr('href'), 'chat-frame',
|
||||
'height=400,width=500,menubar=no,toolbar=no,location=no,status=no,scrollbars=no');
|
||||
evt.preventDefault();
|
||||
evt.returnValue = false;
|
||||
return false;
|
||||
});
|
||||
|
||||
var $startImg = $start.clone(true);
|
||||
$startImg.append(img);
|
||||
$start.text(gettext('Start your Live Chat session.'));
|
||||
|
||||
$container.append($header, $start);
|
||||
$infoList.append($online, $inQueue, $waitTime);
|
||||
|
||||
$status.append($container, $startImg, $infoList);
|
||||
break;
|
||||
case 'FULL':
|
||||
// queue is open but full.
|
||||
$header.text(gettext('The queue is full.'));
|
||||
$container.append($header);
|
||||
img.src = openImage;
|
||||
img.alt = gettext('Our volunteers are busy helping other users.');
|
||||
var $waitTime = $('<li>').text(gettext('Estimated wait: ') + getTimeDisplay(queueStatus['longest-wait']));
|
||||
$infoList.append($waitTime);
|
||||
$status.append($container, img, $infoList);
|
||||
break;
|
||||
case 'CLOSED':
|
||||
case 'READY':
|
||||
default:
|
||||
// queue is effectively closed.
|
||||
$header.text(gettext("We're closed."));
|
||||
$container.append($header);
|
||||
img.src = closedImage;
|
||||
img.alt = gettext('Live Chat is currently closed.');
|
||||
$status.append($container, img);
|
||||
}
|
||||
},
|
||||
dataType: 'xml',
|
||||
cache: false,
|
||||
error: function statusError(xhr, text, e) {
|
||||
$status.html('');
|
||||
$infoList.html('');
|
||||
$container.html('');
|
||||
$header.text(gettext('There was an error checking the queue.'));
|
||||
$container.append($header);
|
||||
$status.append($container);
|
||||
}
|
||||
});
|
||||
}
|
||||
checkQueueStatus();
|
||||
|
||||
setInterval(checkQueueStatus, 10000);
|
||||
});
|
||||
|
||||
function getTimeDisplay(time) {
|
||||
var minutes = Number(time.split(/:/)[0]);
|
||||
if ( minutes < 2 ) {
|
||||
return gettext('Less than 2 minutes.');
|
||||
} else if ( minutes >= 20 ) {
|
||||
return gettext('More than 20 minutes.');
|
||||
}
|
||||
return time;
|
||||
}
|
||||
})();
|
12
settings.py
12
settings.py
|
@ -205,6 +205,7 @@ INSTALLED_APPS = (
|
|||
'gallery',
|
||||
'customercare',
|
||||
'twitter',
|
||||
'chat',
|
||||
)
|
||||
|
||||
# Extra apps for testing
|
||||
|
@ -249,6 +250,7 @@ DOMAIN_METHODS = {
|
|||
'messages': [
|
||||
('apps/forums/**', 'ignore'),
|
||||
('apps/questions/**', 'ignore'),
|
||||
('apps/chat/**', 'ignore'),
|
||||
('apps/**.py',
|
||||
'tower.management.commands.extract.extract_tower_python'),
|
||||
('**/templates/**.html',
|
||||
|
@ -319,6 +321,9 @@ MINIFY_BUNDLES = {
|
|||
'css/jqueryui/jquery.ui.theme.css',
|
||||
'css/customercare.css',
|
||||
),
|
||||
'chat': (
|
||||
'css/chat.css',
|
||||
),
|
||||
},
|
||||
'js': {
|
||||
'common': (
|
||||
|
@ -359,6 +364,9 @@ MINIFY_BUNDLES = {
|
|||
'js/libs/jquery.bullseye-1.0.min.js',
|
||||
'js/customercare.js',
|
||||
),
|
||||
'chat': (
|
||||
'js/chat.js',
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -500,3 +508,7 @@ TWITTER_CONSUMER_KEY = ''
|
|||
TWITTER_CONSUMER_SECRET = ''
|
||||
|
||||
NOTIFICATIONS_FROM_ADDRESS = 'notifications@support.mozilla.com'
|
||||
|
||||
# URL of the chat server.
|
||||
CHAT_SERVER = 'https://chat-support.mozilla.com:9091'
|
||||
CHAT_CACHE_KEY = 'sumo-chat-queue-status'
|
||||
|
|
1
urls.py
1
urls.py
|
@ -20,6 +20,7 @@ urlpatterns = patterns('',
|
|||
(r'^kb', include('wiki.urls')),
|
||||
(r'^gallery', include('gallery.urls')),
|
||||
(r'^army-of-awesome', include('customercare.urls')),
|
||||
(r'^chat', include('chat.urls')),
|
||||
|
||||
# Kitsune admin (not Django admin).
|
||||
(r'^admin/', include('kadmin.urls')),
|
||||
|
|
Загрузка…
Ссылка в новой задаче