Skip to content

Commit

Permalink
[bug 714798] Marketplace AAQ.
Browse files Browse the repository at this point in the history
* New form for marketplace question flow.
* Added zendesk lib to vendor.
* Integrated to zendesk.
* Linked to from marketplace landing page.
* Tests.
  • Loading branch information
rlr committed Jan 17, 2012
1 parent f4afd45 commit e2121e7
Show file tree
Hide file tree
Showing 13 changed files with 401 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .gitmodules
Expand Up @@ -133,3 +133,6 @@
[submodule "vendor/src/django-tastypie"]
path = vendor/src/django-tastypie
url = https://github.com/toastdriven/django-tastypie.git
[submodule "vendor/src/zendesk"]
path = vendor/src/zendesk
url = git://github.com/eventbrite/zendesk.git
6 changes: 6 additions & 0 deletions apps/landings/templates/landings/marketplace.html
Expand Up @@ -8,3 +8,9 @@
{% set top_text = _('Help for Firefox Marketplace') %}
{% set active_site_nav = 'marketplace' %}
{% set h1_text = _('Need Help With <mark>Firefox Marketplace?</mark>') %}

{% block quick_links %}
<li class="aaq">
<a href="{{ url('questions.marketplace_aaq') }}">{{ _('Ask a Question') }}</a>
</li>
{% endblock %}
60 changes: 60 additions & 0 deletions apps/questions/forms.py
Expand Up @@ -58,6 +58,29 @@

REPLY_PLACEHOLDER = _lazy(u'Enter your reply here.')

# Marketplace AAQ form
EMAIL_PLACEHOLDER = _lazy(u'Enter your email address here.')
SUBJECT_PLACEHOLDER = _lazy(u'Enter a subject here.')
SUBJECT_CONTENT_REQUIRED = _lazy(u'Please provide a subject.')
SUBJECT_CONTENT_SHORT = _lazy(u'The subject is too short (%(show_value)s '
u'characters). It must be at least %(limit_value)s '
u'characters.')
SUBJECT_CONTENT_LONG = _lazy(u'Please keep the length of the subject to '
u'%(limit_value)s characters or less. It is '
u'currently %(show_value)s characters.')
BODY_PLACEHOLDER = _lazy(u'Describe your issue here.')
BODY_CONTENT_REQUIRED = _lazy(u'Please describe your issue in the body.')
BODY_CONTENT_SHORT = _lazy(u'The body content is too short (%(show_value)s '
u'characters). It must be at least %(limit_value)s '
u'characters.')
BODY_CONTENT_LONG = _lazy(u'Please keep the length of the body content to '
u'%(limit_value)s characters or less. It is '
u'currently %(show_value)s characters.')
CATEGORY_CHOICES = [(u'account', _lazy(u'Account Issues')),
(u'installation', _lazy(u'Installation Issues')),
(u'payment', _lazy(u'Payment Issues')),
(u'application', _lazy(u'Application Issues')), ]


class EditQuestionForm(forms.Form):
"""Form to edit an existing question"""
Expand Down Expand Up @@ -231,3 +254,40 @@ def clean_email(self):
return self.cleaned_data['email']
# Clear out the email for logged in users, we don't want to use it.
return None


class MarketplaceAaqForm(forms.Form):
"""AAQ Form for Marketplace."""

def __init__(self, user, *args, **kwargs):
super(MarketplaceAaqForm, self).__init__(*args, **kwargs)

# Add email field for users not logged in.
if not user.is_authenticated():
email = forms.EmailField(
label=_lazy(u'Email:'),
widget=forms.TextInput(attrs={'placeholder': EMAIL_PLACEHOLDER})
)
self.fields['email'] = email

subject = StrippedCharField(
label=_lazy(u'Subject:'),
min_length=4,
max_length=255,
widget=forms.TextInput(attrs={'placeholder': SUBJECT_PLACEHOLDER}),
error_messages={'required': SUBJECT_CONTENT_REQUIRED,
'min_length': SUBJECT_CONTENT_SHORT,
'max_length': SUBJECT_CONTENT_LONG})

body = StrippedCharField(
label=_lazy(u'Body:'),
min_length=5,
max_length=10000,
widget=forms.Textarea(attrs={'placeholder': BODY_PLACEHOLDER}),
error_messages={'required': BODY_CONTENT_REQUIRED,
'min_length': BODY_CONTENT_SHORT,
'max_length': BODY_CONTENT_LONG})

category = forms.ChoiceField(
label=_lazy(u'Category:'),
choices=CATEGORY_CHOICES)
64 changes: 64 additions & 0 deletions apps/questions/marketplace.py
@@ -0,0 +1,64 @@
import logging

from django.conf import settings
from django.utils.datastructures import SortedDict

from statsd import statsd
from tower import ugettext_lazy as _lazy
from zendesk import Zendesk, ZendeskError


log = logging.getLogger('k.questions.marketplace')


MARKETPLACE_CATEGORIES = SortedDict([
('payments', _lazy('Payments')),
('applications', _lazy('Applications')),
('account', _lazy('Account')),
])


class ZendeskSettingsError(ZendeskError):
"""Exception for missing settings."""


def submit_ticket(email, category, subject, body):
"""Submit a marketplace ticket to Zendesk.
:arg email: user's email address
:arg category: issue's category
:arg subject: issue's subject
:arg body: issue's description
"""

# Verify required Zendesk settings
zendesk_url = settings.ZENDESK_URL
zendesk_email = settings.ZENDESK_USER_EMAIL
zendesk_password = settings.ZENDESK_USER_PASSWORD
if not zendesk_url or not zendesk_email or not zendesk_password:
log.error('Zendesk settings error: please set ZENDESK_URL, '
'ZENDESK_USER_EMAIL and ZENDESK_USER_PASSWORD.')
statsd.incr('questions.zendesk.settingserror')
raise ZendeskSettingsError('Missing Zendesk settings.')

# Create the Zendesk connection client.
zendesk = Zendesk(zendesk_url, zendesk_email, zendesk_password)

# Create the ticket
new_ticket = {
'ticket': {
'requester_email': email,
'subject': settings.ZENDESK_SUBJECT_PREFIX + subject,
'description': body,
'set_tags': category,
}
}
try:
ticket_url = zendesk.create_ticket(data=new_ticket)
statsd.incr('questions.zendesk.success')
except ZendeskError as e:
log.error('Zendesk error: %s' % e.msg)
statsd.incr('questions.zendesk.error')
raise

return ticket_url
25 changes: 25 additions & 0 deletions apps/questions/templates/questions/marketplace.html
@@ -0,0 +1,25 @@
{# vim: set ts=2 et sts=2 sw=2: #}
{% extends "questions/base.html" %}
{% set title = _('Ask a Question about Firefox Marketplace') %}
{% set classes = 'new-question' %}

{% block content %}
<article class="main">
<div id="ask-question">
<h1>{{ title }}</h1>
<div class="inner-wrap">
<h2>{{ _('Which category best describes your problem?') }}</h2>
<ul class="select-one">
{% for slug, name in categories.items() %}
<li>
<a href="{{ url('questions.marketplace_aaq_category', slug) }}">{{ name }}</a>
</li>
{% endfor %}
</ul>
</div>
</div>
</article>
{% endblock %}

{% block side %}
{% endblock %}
56 changes: 56 additions & 0 deletions apps/questions/templates/questions/marketplace_category.html
@@ -0,0 +1,56 @@
{# vim: set ts=2 et sts=2 sw=2: #}
{% extends "questions/base.html" %}
{% set title = _('Ask a Question about Firefox Marketplace') %}
{% set classes = 'new-question' %}

{% block content %}
<article class="main">
<div id="ask-question">
<h1>{{ title }}</h1>
<div class="inner-wrap">
<div class="selected">
<label>{{ _('Category:') }}</label>
<span>{{ category }}</span>
<a href="{{ url('questions.marketplace_aaq') }}">{{ _('change') }}</a>
</div>

{% if category_slug == 'payments' %}
<h2>
{% trans %}
Please visit the
<a href="https://www.paypal.com/cgi-bin/helpweb?cmd=_help">
Paypal Help Center</a>.
{% endtrans %}
</h2>
{% elif category_slug == 'applications' %}
<h2>
{% trans %}
Please visit the Firefox Market page for the application. There
you can find the developers' contact information and contact them
directly.
{% endtrans %}
</h2>
{% elif category_slug == 'account' %}
{% if error_message %}
<ul class="errorlist">
<li>{{ error_message }}</li>
</ul>
{% endif %}
<form id="question-form" action="" method="post">
{{ csrf() }}
<ol>
{{ form.as_ul() }}
<li class="submit">
<input type="submit" class="btn btn-important" value="{{ _('Submit Issue') }}" />
</li>
</ol>
</form>
{% endif %}

</div>
</div>
</article>
{% endblock %}

{% block side %}
{% endblock %}
23 changes: 23 additions & 0 deletions apps/questions/templates/questions/marketplace_success.html
@@ -0,0 +1,23 @@
{# vim: set ts=2 et sts=2 sw=2: #}
{% extends "questions/base.html" %}
{% set title = _('Issue submitted successfully') %}
{% set classes = 'new-question' %}

{% block content %}
<article class="main">
<div id="ask-question">
<h1>{{ title }}</h1>
<div class="inner-wrap">
<p>
{% trans %}
Your issue has been submitted successfully. You should receive
a confirmation email and hear back from us shortly.
{% endtrans %}
</p>
</div>
</div>
</article>
{% endblock %}

{% block side %}
{% endblock %}
91 changes: 91 additions & 0 deletions apps/questions/tests/test_marketplace.py
@@ -0,0 +1,91 @@
import mock
from nose.tools import eq_
from pyquery import PyQuery as pq

import questions.views
from sumo.tests import TestCase, LocalizingClient, get, post
from users.tests import user


class MarketplaceAaqTests(TestCase):
client_class = LocalizingClient

def setUp(self):
super(MarketplaceAaqTests, self).setUp()

self.user = user(email='s@s.com', save=True)
self.client.login(username=self.user.username, password='testpass')

def test_aaq_page(self):
"""Verify the initial AAQ page."""
response = get(self.client, 'questions.marketplace_aaq')
eq_(200, response.status_code)
doc = pq(response.content)
eq_(3, len(doc('ul.select-one li')))

def test_invalid_category(self):
"""Invalid category slug should result in 404"""
response = get(self.client,
'questions.marketplace_aaq_category',
args=['invalid-category'])
eq_(404, response.status_code)

def test_payments_category(self):
"""Verify the payments category page."""
response = get(self.client,
'questions.marketplace_aaq_category',
args=['payments'])
eq_(200, response.status_code)
doc = pq(response.content)
assert 'Paypal Help Center' in doc('div.inner-wrap').text()

def test_account_category(self):
"""Verify the account category page."""
response = get(self.client,
'questions.marketplace_aaq_category',
args=['account'])
eq_(200, response.status_code)
doc = pq(response.content)
eq_(4, len(doc('#question-form li')))

def test_account_category_anon(self):
"""Verify the account category page with unauth'd user."""
self.client.logout()
response = get(self.client,
'questions.marketplace_aaq_category',
args=['account'])
eq_(200, response.status_code)
doc = pq(response.content)
# One extra form field (email) in this case.
eq_(5, len(doc('#question-form li')))

@mock.patch.object(questions.views, 'submit_ticket')
def test_submit_ticket(self, submit_ticket):
"""Verify form post."""
subject = 'A new ticket'
body = 'Lorem ipsum dolor sit amet'
cat = 'account'

response = post(self.client,
'questions.marketplace_aaq_category',
{'subject': subject, 'body': body, 'category': cat},
args=['account'])
eq_(200, response.status_code)
submit_ticket.assert_called_with(self.user.email, cat, subject, body)

@mock.patch.object(questions.views, 'submit_ticket')
def test_submit_ticket_anon(self, submit_ticket):
"""Verify form post from unauth'd user."""
email = 'foo@bar.com'
subject = 'A new ticket'
body = 'Lorem ipsum dolor sit amet'
cat = 'account'

self.client.logout()
response = post(self.client,
'questions.marketplace_aaq_category',
{'subject': subject, 'body': body, 'category': cat,
'email': email},
args=['account'])
eq_(200, response.status_code)
submit_ticket.assert_called_with(email, cat, subject, body)
7 changes: 7 additions & 0 deletions apps/questions/urls.py
Expand Up @@ -12,6 +12,13 @@
url(r'^/answer-preview-async$', 'answer_preview_async',
name='questions.answer_preview_async'),

# AAQ flow for Marketplace
url(r'^/marketplace$', 'marketplace', name='questions.marketplace_aaq'),
url(r'^/marketplace/success$',
'marketplace_success', name='questions.marketplace_aaq_success'),
url(r'^/marketplace/(?P<category_slug>[\w\-]+)$',
'marketplace_category', name='questions.marketplace_aaq_category'),

# TODO: Factor out `/(?P<question_id>\d+)` below
url(r'^/(?P<question_id>\d+)$', 'answers', name='questions.answers'),
url(r'^/(?P<question_id>\d+)/edit$',
Expand Down

0 comments on commit e2121e7

Please sign in to comment.