Skip to content
This repository has been archived by the owner on Feb 8, 2018. It is now read-only.

Commit

Permalink
Clean up email listing
Browse files Browse the repository at this point in the history
  • Loading branch information
chadwhitacre committed Sep 8, 2017
1 parent cdeb2ab commit dad87d6
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 83 deletions.
11 changes: 11 additions & 0 deletions gratipay/models/participant/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ class Email(object):
"""

@property
def has_pending_email_address_verifications(self):
"""A boolean indicating whether there are email address verifications
outstanding for this participant. Makes a db call.
"""
for email in self.get_emails():
if not email.verified:
return True
return False


def start_email_verification(self, email, *packages):
"""Add an email address for a participant.
Expand Down
4 changes: 3 additions & 1 deletion gratipay/testing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
from .billing import BillingHarness
from .browser import BrowserHarness
from .deploy_hooks import DeployHooksHarness
from .email import SentEmailHarness, QueuedEmailHarness

__all__ = ['Harness', 'BillingHarness', 'BrowserHarness', 'DeployHooksHarness', 'D','P','T']
__all__ = [ 'Harness', 'BillingHarness', 'BrowserHarness', 'DeployHooksHarness', 'SentEmailHarness'
, 'QueuedEmailHarness', 'D','P','T' ]


class Foobar(Exception): pass
Expand Down
18 changes: 12 additions & 6 deletions gratipay/testing/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import time

from splinter.browser import _DRIVERS
from selenium.common.exceptions import StaleElementReferenceException

from gratipay.security import user

Expand Down Expand Up @@ -55,9 +56,10 @@ def tearDown(self):
self.cookies.delete()

def visit(self, url):
"""Extend to prefix our base URL.
"""Extend to prefix our base URL if necessary.
"""
return self._browser.visit(self.base_url + url)
base_url = '' if url.startswith('http') else self.base_url
return self._browser.visit(base_url + url)

def sign_in(self, username):
"""Given a username, sign in the user.
Expand All @@ -71,10 +73,10 @@ def sign_in(self, username):
P(username).update_session(token, expires)
self.cookies.add({user.SESSION: token})

def css(self, selector):
def css(self, selector, element=None):
"""Shortcut for find_by_css.
"""
return self.find_by_css(selector)
return (element or self).find_by_css(selector)

def js(self, code):
"""Shortcut for evaluate_script.
Expand Down Expand Up @@ -109,8 +111,12 @@ def wait_for(self, selector, timeout=2):
while time.time() < end_time:
if self.has_element(selector):
element = self.find_by_css(selector)
if element.visible:
return element
try:
if element.visible:
return element
except StaleElementReferenceException:
# May need to wait across page reload.
pass
raise NeverShowedUp(selector)

def wait_for_notification(self, type='notice', message=None, timeout=2):
Expand Down
12 changes: 11 additions & 1 deletion gratipay/testing/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,23 @@ class QueuedEmailHarness(_AbstractEmailHarness):
"""An email harness that pulls from the ``email_messages`` table.
"""

_SELECT = 'SELECT * FROM email_messages WHERE result is null ORDER BY ctime DESC LIMIT 1'

def _get_last_email(self):
rec = self.db.one('SELECT * FROM email_messages ORDER BY ctime DESC LIMIT 1')
rec = self.db.one(self._SELECT)
return self.app.email_queue._prepare_email_message_for_ses(rec)

def count_email_messages(self):
return self.db.one('SELECT count(*) FROM email_messages WHERE result is null')

def pop_email_message(self):
"""Same as get_last_email but also marks as sent in the db.
"""
out = self.get_last_email()
rec = self.db.one(self._SELECT)
self.app.email_queue._store_result(rec.id, '', 'deadbeef')
return out


class SentEmailHarness(_AbstractEmailHarness):
"""An email harness that patches ``_mailer.send_email`` to ``get_last_email``
Expand Down
53 changes: 34 additions & 19 deletions js/gratipay/emails.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
Gratipay.emails = {};

Gratipay.emails.post = function(e) {
e.preventDefault();
Gratipay.emails.post = function() {
var $this = $(this);
var action = this.className;
var $inputs = $('.emails button, .emails input');
var address = $this.parent().data('email') || $('input.add-email').val();
var $row = $this.closest('tr');
var address = $row.data('email') || $('.add input').val();

$inputs.prop('disabled', true);

Expand All @@ -15,19 +15,16 @@ Gratipay.emails.post = function(e) {
data: {action: action, address: address},
dataType: 'json',
success: function (msg) {
if (msg) {
Gratipay.notification(msg, 'success');
}
if (action == 'add-email') {
$('input.add-email').val('');
setTimeout(function(){ window.location.reload(); }, 3000);
return;
} else if (action == 'set-primary') {
$('.emails li').removeClass('primary');
$this.parent().addClass('primary');
} else if (action == 'remove') {
$this.parent().fadeOut();
}
switch(action) {
case 'resend':
Gratipay.notification(msg, 'success');
break;
case 'remove':
$row.slideUp(function() { $row.remove() });
break;
default:
window.location.reload();
};
$inputs.prop('disabled', false);
},
error: [
Expand All @@ -37,15 +34,33 @@ Gratipay.emails.post = function(e) {
});
};

Gratipay.emails.showAddForm = function() {
$('.add-form-knob').hide();
$('.add form').show();
}

Gratipay.emails.hideAddForm = function() {
$('.add form').hide();
$('.add-form-knob').show();
}

Gratipay.emails.handleAddForm = function(e) {
e.preventDefault();
if (e.type === 'submit')
Gratipay.emails.post.call(this);
else
Gratipay.emails.hideAddForm();
}

Gratipay.emails.init = function () {
Gratipay.emails.init = function() {

// Wire up email addresses list.
// =============================

$('.emails button, .emails input').prop('disabled', false);
$('.emails button[class]').on('click', Gratipay.emails.post);
$('form.add-email').on('submit', Gratipay.emails.post);
$('.emails tr.existing button').click(Gratipay.emails.post);
$('button.add').click(Gratipay.emails.showAddForm);
$('.add form').on('submit reset', Gratipay.emails.handleAddForm);


// Wire up notification preferences
Expand Down
33 changes: 0 additions & 33 deletions scss/components/emails.scss

This file was deleted.

40 changes: 40 additions & 0 deletions scss/pages/emails.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#emails #content {
.icon {
color: $black;
position: absolute;
top: 12px;
left: 16px;
font-size: 24px;
line-height: 24px;
}
button {
color: $medium-gray;
font: normal 12px/14px $Ideal;
background: $light-gray;
padding: 3px 6px;
position: relative;
z-index: 2;
&:hover { color: $white; }
&.set-primary:hover { background: $blue; }
&.remove:hover { background: $red; }
&.add:hover { background: $green; }
&.resend:hover { background: $green; }
}
.add {
form {
display: none; /* off to start, turn on via javascript */

input {
outline: none;
border: none;
border-bottom: 1px solid $black;
padding: 0;
}

button {
color: $white;
background: $green;
}
}
}
}
104 changes: 104 additions & 0 deletions tests/ttw/test_email_verification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function, unicode_literals

import re
from gratipay.testing import BrowserHarness, QueuedEmailHarness


class Tests(BrowserHarness, QueuedEmailHarness):


def test(self):
self.alice = self.make_participant('alice', claimed_time='now')
self.sign_in('alice')
self.visit('/~alice/emails/')

# Helpers

buttons = lambda: [b.text for b in self.css('button', row)]
click = lambda text: [b for b in self.css('button', row) if b.text == text][0].click()

def add(email_address):
click('Add an email address')
self.css('input', row).fill(email_address)
click('Send verification')

def get_verification_link():
verification_email = self.pop_email_message()
verification_link = re.match( r'.*?(http.*?$).*'
, verification_email['body_text']
, re.DOTALL | re.MULTILINE
).groups()[0]
return verification_link


# Starts clean.
rows = self.css('#content tr')
assert len(rows) == 1
row = rows[0]
assert buttons() == ['Add an email address', '', '']

# Can toggle add form on and off and on again.
click('Add an email address')
assert buttons() == ['', 'Send verification', 'Cancel']
click('Cancel')
assert buttons() == ['Add an email address', '', '']

# Can submit add form.
add('[email protected]')
row = self.wait_for('tr.existing')
assert buttons() == ['Resend verification']
self.pop_email_message() # throw away verification message

# Can resend verification.
click('Resend verification')
assert self.wait_for_success() == 'Check your inbox for a verification link.'

# Can verify.
self.visit(get_verification_link())
assert self.css('#content h1').text == 'Success!'

# Now listed as primary -- nothing to be done with primary.
self.visit('/~alice/emails/')
rows = self.css('.emails.listing tr')
assert len(rows) == 2
row = rows[0]
assert buttons() == []
assert row.text.endswith('Your primary email address')

# ... but the add form is back. Can we re-add the same?
row = rows[1]
add('[email protected]')
assert self.wait_for_error() == 'You have already added and verified that address.' # No!
click('Cancel')

# Let's add another!
add('[email protected]')
self.wait_for('.emails.listing tr')
assert self.pop_email_message()['subject'] == 'New activity on your account'
self.visit(get_verification_link())

# Now we should have a primary and a linked address, and an add row.
self.css('#content a').click()
rows = self.wait_for('.emails.listing tr')
assert len(rows) == 3
email_addresses = [x.text for x in self.css('.existing .listing-name')]
assert email_addresses == ['[email protected]', '[email protected]']
row = rows[2]
assert buttons() == ['Add an email address', '', '']

# We can promote the secondary account to primary.
row = rows[1]
click('Set as primary')
rows = self.wait_for('.emails.listing tr')
assert len(rows) == 3

# ... and now the order is reversed.
email_addresses = [x.text for x in self.css('.existing .listing-name')]
assert email_addresses == ['[email protected]', '[email protected]']

# We can remove the (new) secondary account.
row = rows[1]
click('Remove')
self.wait_to_disappear('tr[data-email="[email protected]"]')
assert len(self.css('.emails.listing tr')) == 2
2 changes: 1 addition & 1 deletion www/assets/gratipay.css.spt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
@import "scss/components/cta";
@import "scss/components/danger-zone";
@import "scss/components/dropdown";
@import "scss/components/emails";
@import "scss/components/github-ribbon";
@import "scss/components/js-edit";
@import "scss/components/linear_gradient";
Expand Down Expand Up @@ -65,6 +64,7 @@
@import "scss/layouts/layout";
@import "scss/layouts/responsiveness";

@import "scss/pages/emails";
@import "scss/pages/homepage";
@import "scss/pages/history";
@import "scss/pages/identities";
Expand Down
Loading

0 comments on commit dad87d6

Please sign in to comment.