From 018ed559fafe3850850f5bd1b0dc34f967c9404f Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Thu, 18 Jun 2015 22:33:05 -0400 Subject: [PATCH 01/19] Failing test for storing team review_url --- tests/py/test_teams.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/py/test_teams.py b/tests/py/test_teams.py index 3d7a47cf88..53d338f440 100644 --- a/tests/py/test_teams.py +++ b/tests/py/test_teams.py @@ -57,6 +57,7 @@ def test_all_fields_persist(self): assert team.product_or_service == 'We make widgets.' assert team.onboarding_url == 'http://inside.gratipay.com/' assert team.todo_url == 'https://github.com/gratipay' + assert team.review_url is None def test_casing_of_urls_survives(self): self.make_participant('alice', claimed_time='now', email_address='', last_paypal_result='') From d6df5e80b9f840c1441afc737fcafc58028ded1e Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Thu, 18 Jun 2015 22:44:41 -0400 Subject: [PATCH 02/19] Add review_url column to teams table --- sql/branch.sql | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 sql/branch.sql diff --git a/sql/branch.sql b/sql/branch.sql new file mode 100644 index 0000000000..945329ec57 --- /dev/null +++ b/sql/branch.sql @@ -0,0 +1,3 @@ +BEGIN; + ALTER TABLE teams ADD COLUMN review_url text DEFAULT NULL; +END; From 57b4e1b223f560a8064430a9d911ab31173acdba Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Thu, 18 Jun 2015 22:58:27 -0400 Subject: [PATCH 03/19] More failing tests for review_url --- tests/py/test_teams.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/py/test_teams.py b/tests/py/test_teams.py index 53d338f440..b5ef95b6b1 100644 --- a/tests/py/test_teams.py +++ b/tests/py/test_teams.py @@ -7,6 +7,9 @@ from gratipay.models.team import Team, AlreadyMigrated +REVIEW_URL = "https://github.com/gratipay/inside.gratipay.com/issues/270" + + class TestTeams(Harness): valid_data = { @@ -43,10 +46,11 @@ def test_can_construct_from_id(self): def test_can_create_new_team(self): self.make_participant('alice', claimed_time='now', email_address='', last_paypal_result='') - self.post_new(dict(self.valid_data)) + r = self.post_new(dict(self.valid_data)) team = self.db.one("SELECT * FROM teams") assert team assert team.owner == 'alice' + assert r['review_url'] == team.review_url def test_all_fields_persist(self): self.make_participant('alice', claimed_time='now', email_address='', last_paypal_result='') @@ -55,9 +59,7 @@ def test_all_fields_persist(self): assert team.name == 'Gratiteam' assert team.homepage == 'http://gratipay.com/' assert team.product_or_service == 'We make widgets.' - assert team.onboarding_url == 'http://inside.gratipay.com/' - assert team.todo_url == 'https://github.com/gratipay' - assert team.review_url is None + assert team.review_url == REVIEW_URL def test_casing_of_urls_survives(self): self.make_participant('alice', claimed_time='now', email_address='', last_paypal_result='') From a280a0eab9c42276b0ae519e33a9f2414045c8a8 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Thu, 18 Jun 2015 23:06:23 -0400 Subject: [PATCH 04/19] Return review_url from create.json Instead of email. --- gratipay/models/team.py | 8 ++++++++ tests/py/test_teams.py | 3 ++- www/teams/create.json.spt | 6 ++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/gratipay/models/team.py b/gratipay/models/team.py index 98a94f93b3..237d18b12a 100644 --- a/gratipay/models/team.py +++ b/gratipay/models/team.py @@ -68,6 +68,14 @@ def insert(cls, owner, **fields): """, fields) + + def generate_review_url(self): + review_url = "https://github.com/gratipay/inside.gratipay.com/issues/270" + self.db.run("UPDATE teams SET review_url=%s WHERE id=%s", (review_url, self.id)) + self.set_attributes(review_url=review_url) + return review_url + + def get_og_title(self): out = self.name receiving = self.receiving diff --git a/tests/py/test_teams.py b/tests/py/test_teams.py index b5ef95b6b1..2104d6863a 100644 --- a/tests/py/test_teams.py +++ b/tests/py/test_teams.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import json import pytest from decimal import Decimal @@ -50,7 +51,7 @@ def test_can_create_new_team(self): team = self.db.one("SELECT * FROM teams") assert team assert team.owner == 'alice' - assert r['review_url'] == team.review_url + assert json.loads(r.body)['review_url'] == team.review_url def test_all_fields_persist(self): self.make_participant('alice', claimed_time='now', email_address='', last_paypal_result='') diff --git a/www/teams/create.json.spt b/www/teams/create.json.spt index 1bb8df0783..adbc14c637 100644 --- a/www/teams/create.json.spt +++ b/www/teams/create.json.spt @@ -54,9 +54,11 @@ if request.method == 'POST': fields['slug'] = slugize(fields['name']) try: - Team.insert(user.participant, **fields) + team = Team.insert(user.participant, **fields) except IntegrityError: raise Response(400, _("Sorry, there is already a team using '{}'.", fields['slug'])) + review_url = team.generate_review_url() + [---] application/json via json_dump -{'email': user.participant.email_address} +{'review_url': review_url} From b66ff4ef21d850e9ffa346d9c79e147d5f60ca8a Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Thu, 18 Jun 2015 23:54:36 -0400 Subject: [PATCH 05/19] It works! :) https://github.com/gratipay/inside.gratipay.com/issues/267 --- gratipay/models/team.py | 21 ++++++++++++++++++++- tests/py/fixtures/TestNewTeams.yml | 27 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 tests/py/fixtures/TestNewTeams.yml diff --git a/gratipay/models/team.py b/gratipay/models/team.py index 237d18b12a..55fbcbf543 100644 --- a/gratipay/models/team.py +++ b/gratipay/models/team.py @@ -1,5 +1,6 @@ """Teams on Gratipay receive payments and distribute payroll. """ +import requests from postgres.orm import Model status_icons = { "unreviewed": "✋" @@ -70,7 +71,25 @@ def insert(cls, owner, **fields): def generate_review_url(self): - review_url = "https://github.com/gratipay/inside.gratipay.com/issues/270" + + import json, os + auth = (os.environ['TEAM_REVIEW_USERNAME'], os.environ['TEAM_REVIEW_TOKEN']) + data = json.dumps({ "title": "review {}".format(self.name) + , "body": "https://gratipay.com/{}/".format(self.slug) + , "labels": ["Review"] + }) + r = requests.post( "https://api.github.com/repos/gratipay/inside.gratipay.com/issues" + , auth=auth + , data=data + ) + if r.status_code != 201: + raise + + review_url = r.json()['html_url'] + return self.update_review_url(review_url) + + + def update_review_url(self, review_url): self.db.run("UPDATE teams SET review_url=%s WHERE id=%s", (review_url, self.id)) self.set_attributes(review_url=review_url) return review_url diff --git a/tests/py/fixtures/TestNewTeams.yml b/tests/py/fixtures/TestNewTeams.yml new file mode 100644 index 0000000000..e040bc2e18 --- /dev/null +++ b/tests/py/fixtures/TestNewTeams.yml @@ -0,0 +1,27 @@ +interactions: +- request: + body: '{"body": "https://gratipay.com/TheATeam/", "labels": ["Review"], "title": + "review The A Team"}' + headers: {} + method: POST + uri: https://api.github.com:443/repos/gratipay/inside.gratipay.com/issues + response: + body: {string: !!python/unicode '{"url":"https://api.github.com/repos/gratipay/inside.gratipay.com/issues/267","labels_url":"https://api.github.com/repos/gratipay/inside.gratipay.com/issues/267/labels{/name}","comments_url":"https://api.github.com/repos/gratipay/inside.gratipay.com/issues/267/comments","events_url":"https://api.github.com/repos/gratipay/inside.gratipay.com/issues/267/events","html_url":"https://github.com/gratipay/inside.gratipay.com/issues/267","id":89458142,"number":267,"title":"review + The A Team","user":{"login":"gratipay-gremlin","id":12961261,"avatar_url":"https://avatars.githubusercontent.com/u/12961261?v=3","gravatar_id":"","url":"https://api.github.com/users/gratipay-gremlin","html_url":"https://github.com/gratipay-gremlin","followers_url":"https://api.github.com/users/gratipay-gremlin/followers","following_url":"https://api.github.com/users/gratipay-gremlin/following{/other_user}","gists_url":"https://api.github.com/users/gratipay-gremlin/gists{/gist_id}","starred_url":"https://api.github.com/users/gratipay-gremlin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gratipay-gremlin/subscriptions","organizations_url":"https://api.github.com/users/gratipay-gremlin/orgs","repos_url":"https://api.github.com/users/gratipay-gremlin/repos","events_url":"https://api.github.com/users/gratipay-gremlin/events{/privacy}","received_events_url":"https://api.github.com/users/gratipay-gremlin/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/gratipay/inside.gratipay.com/labels/Review","name":"Review","color":"CC0099"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":0,"created_at":"2015-06-19T03:51:39Z","updated_at":"2015-06-19T03:51:39Z","closed_at":null,"body":"https://gratipay.com/TheATeam/","closed_by":null}'} + headers: + access-control-allow-credentials: ['true'] + access-control-allow-origin: ['*'] + access-control-expose-headers: ['ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, + X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, + X-Poll-Interval'] + cache-control: ['private, max-age=60, s-maxage=60'] + content-length: ['1815'] + content-security-policy: [default-src 'none'] + content-type: [application/json; charset=utf-8] + etag: ['"c4c0ab7123769a17be70b808d89d8eac"'] + location: ['https://api.github.com/repos/gratipay/inside.gratipay.com/issues/267'] + status: [201 Created] + strict-transport-security: [max-age=31536000; includeSubdomains; preload] + vary: ['Accept, Authorization, Cookie, X-GitHub-OTP', Accept-Encoding] + status: {code: 201, message: Created} +version: 1 From e9cb2bdc7282c5551cf0403fb29b6a6ac3c61d32 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Fri, 19 Jun 2015 07:22:00 -0400 Subject: [PATCH 06/19] Update test fixture for new dedicated repo --- tests/py/fixtures/TestNewTeams.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/py/fixtures/TestNewTeams.yml b/tests/py/fixtures/TestNewTeams.yml index e040bc2e18..b9201243cf 100644 --- a/tests/py/fixtures/TestNewTeams.yml +++ b/tests/py/fixtures/TestNewTeams.yml @@ -1,13 +1,12 @@ interactions: - request: - body: '{"body": "https://gratipay.com/TheATeam/", "labels": ["Review"], "title": - "review The A Team"}' + body: '{"body": "https://gratipay.com/TheATeam/", "title": "review The A Team"}' headers: {} method: POST - uri: https://api.github.com:443/repos/gratipay/inside.gratipay.com/issues + uri: https://api.github.com:443/repos/gratipay/review/issues response: - body: {string: !!python/unicode '{"url":"https://api.github.com/repos/gratipay/inside.gratipay.com/issues/267","labels_url":"https://api.github.com/repos/gratipay/inside.gratipay.com/issues/267/labels{/name}","comments_url":"https://api.github.com/repos/gratipay/inside.gratipay.com/issues/267/comments","events_url":"https://api.github.com/repos/gratipay/inside.gratipay.com/issues/267/events","html_url":"https://github.com/gratipay/inside.gratipay.com/issues/267","id":89458142,"number":267,"title":"review - The A Team","user":{"login":"gratipay-gremlin","id":12961261,"avatar_url":"https://avatars.githubusercontent.com/u/12961261?v=3","gravatar_id":"","url":"https://api.github.com/users/gratipay-gremlin","html_url":"https://github.com/gratipay-gremlin","followers_url":"https://api.github.com/users/gratipay-gremlin/followers","following_url":"https://api.github.com/users/gratipay-gremlin/following{/other_user}","gists_url":"https://api.github.com/users/gratipay-gremlin/gists{/gist_id}","starred_url":"https://api.github.com/users/gratipay-gremlin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gratipay-gremlin/subscriptions","organizations_url":"https://api.github.com/users/gratipay-gremlin/orgs","repos_url":"https://api.github.com/users/gratipay-gremlin/repos","events_url":"https://api.github.com/users/gratipay-gremlin/events{/privacy}","received_events_url":"https://api.github.com/users/gratipay-gremlin/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/gratipay/inside.gratipay.com/labels/Review","name":"Review","color":"CC0099"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":0,"created_at":"2015-06-19T03:51:39Z","updated_at":"2015-06-19T03:51:39Z","closed_at":null,"body":"https://gratipay.com/TheATeam/","closed_by":null}'} + body: {string: !!python/unicode '{"url":"https://api.github.com/repos/gratipay/review/issues/2","labels_url":"https://api.github.com/repos/gratipay/review/issues/2/labels{/name}","comments_url":"https://api.github.com/repos/gratipay/review/issues/2/comments","events_url":"https://api.github.com/repos/gratipay/review/issues/2/events","html_url":"https://github.com/gratipay/review/issues/2","id":89539256,"number":2,"title":"review + The A Team","user":{"login":"gratipay-gremlin","id":12961261,"avatar_url":"https://avatars.githubusercontent.com/u/12961261?v=3","gravatar_id":"","url":"https://api.github.com/users/gratipay-gremlin","html_url":"https://github.com/gratipay-gremlin","followers_url":"https://api.github.com/users/gratipay-gremlin/followers","following_url":"https://api.github.com/users/gratipay-gremlin/following{/other_user}","gists_url":"https://api.github.com/users/gratipay-gremlin/gists{/gist_id}","starred_url":"https://api.github.com/users/gratipay-gremlin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gratipay-gremlin/subscriptions","organizations_url":"https://api.github.com/users/gratipay-gremlin/orgs","repos_url":"https://api.github.com/users/gratipay-gremlin/repos","events_url":"https://api.github.com/users/gratipay-gremlin/events{/privacy}","received_events_url":"https://api.github.com/users/gratipay-gremlin/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":0,"created_at":"2015-06-19T11:19:55Z","updated_at":"2015-06-19T11:19:55Z","closed_at":null,"body":"https://gratipay.com/TheATeam/","closed_by":null}'} headers: access-control-allow-credentials: ['true'] access-control-allow-origin: ['*'] @@ -15,11 +14,11 @@ interactions: X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval'] cache-control: ['private, max-age=60, s-maxage=60'] - content-length: ['1815'] + content-length: ['1624'] content-security-policy: [default-src 'none'] content-type: [application/json; charset=utf-8] - etag: ['"c4c0ab7123769a17be70b808d89d8eac"'] - location: ['https://api.github.com/repos/gratipay/inside.gratipay.com/issues/267'] + etag: ['"38c0f12312600b8f603f62407c845381"'] + location: ['https://api.github.com/repos/gratipay/review/issues/2'] status: [201 Created] strict-transport-security: [max-age=31536000; includeSubdomains; preload] vary: ['Accept, Authorization, Cookie, X-GitHub-OTP', Accept-Encoding] From adcf4b23ad866c0d7b2f706fad386ac74041cbf1 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Fri, 19 Jun 2015 07:23:13 -0400 Subject: [PATCH 07/19] Post to review repo instead of Review label in IG --- gratipay/models/team.py | 3 +-- tests/py/test_teams.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/gratipay/models/team.py b/gratipay/models/team.py index 55fbcbf543..5e7f36aed0 100644 --- a/gratipay/models/team.py +++ b/gratipay/models/team.py @@ -76,9 +76,8 @@ def generate_review_url(self): auth = (os.environ['TEAM_REVIEW_USERNAME'], os.environ['TEAM_REVIEW_TOKEN']) data = json.dumps({ "title": "review {}".format(self.name) , "body": "https://gratipay.com/{}/".format(self.slug) - , "labels": ["Review"] }) - r = requests.post( "https://api.github.com/repos/gratipay/inside.gratipay.com/issues" + r = requests.post( "https://api.github.com/repos/gratipay/review/issues" , auth=auth , data=data ) diff --git a/tests/py/test_teams.py b/tests/py/test_teams.py index 2104d6863a..ff1a6f4e55 100644 --- a/tests/py/test_teams.py +++ b/tests/py/test_teams.py @@ -8,7 +8,7 @@ from gratipay.models.team import Team, AlreadyMigrated -REVIEW_URL = "https://github.com/gratipay/inside.gratipay.com/issues/270" +REVIEW_URL = "https://github.com/gratipay/review/issues/2" class TestTeams(Harness): From be9e90fd457a7d851a2780935607c6e2f8719f64 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Fri, 19 Jun 2015 07:23:46 -0400 Subject: [PATCH 08/19] Here's a really nice debug helper for HTTP --- gratipay/models/team.py | 4 +++- gratipay/testing/__init__.py | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/gratipay/models/team.py b/gratipay/models/team.py index 5e7f36aed0..7753d8d890 100644 --- a/gratipay/models/team.py +++ b/gratipay/models/team.py @@ -82,7 +82,9 @@ def generate_review_url(self): , data=data ) if r.status_code != 201: - raise + print(r.status_code) + print(r.text) + raise Heck review_url = r.json()['html_url'] return self.update_review_url(review_url) diff --git a/gratipay/testing/__init__.py b/gratipay/testing/__init__.py index d127dbbe32..257f9095f4 100644 --- a/gratipay/testing/__init__.py +++ b/gratipay/testing/__init__.py @@ -310,3 +310,30 @@ def get_tip(self, tipper, tippee): class Foobar(Exception): pass + + +def debug_http(): + """Turns on debug logging for HTTP traffic. Happily, this includes VCR usage. + + http://stackoverflow.com/a/16630836 + + """ + import logging + + # These two lines enable debugging at httplib level + # (requests->urllib3->http.client) You will see the REQUEST, including + # HEADERS and DATA, and RESPONSE with HEADERS but without DATA. The + # only thing missing will be the response.body which is not logged. + try: + import http.client as http_client + except ImportError: + # Python 2 + import httplib as http_client + http_client.HTTPConnection.debuglevel = 1 + + # You must initialize logging, otherwise you'll not see debug output. + logging.basicConfig() + logging.getLogger().setLevel(logging.DEBUG) + requests_log = logging.getLogger("requests.packages.urllib3") + requests_log.setLevel(logging.DEBUG) + requests_log.propagate = True From 9d354ea7db8c3085660e61b743ae4733ee552784 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Fri, 19 Jun 2015 07:23:59 -0400 Subject: [PATCH 09/19] Change envvar names (less teams in Gratipay 2.1) --- gratipay/models/team.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gratipay/models/team.py b/gratipay/models/team.py index 7753d8d890..9beb39273b 100644 --- a/gratipay/models/team.py +++ b/gratipay/models/team.py @@ -73,7 +73,7 @@ def insert(cls, owner, **fields): def generate_review_url(self): import json, os - auth = (os.environ['TEAM_REVIEW_USERNAME'], os.environ['TEAM_REVIEW_TOKEN']) + auth = (os.environ['REVIEW_USERNAME'], os.environ['REVIEW_TOKEN']) data = json.dumps({ "title": "review {}".format(self.name) , "body": "https://gratipay.com/{}/".format(self.slug) }) From 82dff10cefcb19ea2ff19b381ce202c4a4600fc8 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Thu, 2 Jul 2015 19:17:43 -0400 Subject: [PATCH 10/19] Implement review configuration properly --- defaults.env | 4 ++++ gratipay/main.py | 1 + gratipay/models/team.py | 9 +++------ gratipay/wireup.py | 9 +++++++++ 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/defaults.env b/defaults.env index 709f9bf3e8..1aec797ea6 100644 --- a/defaults.env +++ b/defaults.env @@ -76,4 +76,8 @@ GUNICORN_OPTS="--workers=1 --timeout=99999999" MANDRILL_KEY=Phh_Lm3RdPT5blqOPY4dVQ +REVIEW_REPO= +REVIEW_USERNAME= +REVIEW_TOKEN= + RAISE_SIGNIN_NOTIFICATIONS=no diff --git a/gratipay/main.py b/gratipay/main.py index a2b5a4f301..1d481402f8 100644 --- a/gratipay/main.py +++ b/gratipay/main.py @@ -64,6 +64,7 @@ gratipay.wireup.base_url(website, env) gratipay.wireup.secure_cookies(env) gratipay.wireup.billing(env) +gratipay.wireup.team_review(env) gratipay.wireup.username_restrictions(website) gratipay.wireup.load_i18n(website.project_root, tell_sentry) gratipay.wireup.other_stuff(website, env) diff --git a/gratipay/models/team.py b/gratipay/models/team.py index 9beb39273b..2917f9c7e2 100644 --- a/gratipay/models/team.py +++ b/gratipay/models/team.py @@ -2,6 +2,7 @@ """ import requests from postgres.orm import Model +from aspen import json status_icons = { "unreviewed": "✋" , "rejected": "❌" @@ -72,15 +73,11 @@ def insert(cls, owner, **fields): def generate_review_url(self): - import json, os - auth = (os.environ['REVIEW_USERNAME'], os.environ['REVIEW_TOKEN']) + api_url = "https://api.github.com/repos/{}/issues".format(self.review_repo) data = json.dumps({ "title": "review {}".format(self.name) , "body": "https://gratipay.com/{}/".format(self.slug) }) - r = requests.post( "https://api.github.com/repos/gratipay/review/issues" - , auth=auth - , data=data - ) + r = requests.post(api_url, auth=self.review_auth, data=data) if r.status_code != 201: print(r.status_code) print(r.text) diff --git a/gratipay/wireup.py b/gratipay/wireup.py index 38049ed3a7..d4076a645b 100644 --- a/gratipay/wireup.py +++ b/gratipay/wireup.py @@ -84,6 +84,12 @@ def billing(env): env.braintree_private_key ) + +def team_review(env): + Team.review_repo = env.review_repo + Team.review_auth = (env.review_username, env.review_token) + + def username_restrictions(website): gratipay.RESTRICTED_USERNAMES = os.listdir(website.www_root) @@ -402,6 +408,9 @@ def env(): LOG_METRICS = is_yesish, INCLUDE_PIWIK = is_yesish, MANDRILL_KEY = unicode, + REVIEW_REPO = unicode, + REVIEW_USERNAME = unicode, + REVIEW_TOKEN = unicode, RAISE_SIGNIN_NOTIFICATIONS = is_yesish, # This is used in our Procfile. (PORT is also used but is provided by From a3e6af442fb8f4a29489b9779a7b67c736b153da Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Thu, 2 Jul 2015 19:38:33 -0400 Subject: [PATCH 11/19] Mock out the GitHub call except for one test --- gratipay/models/team.py | 12 +++++------- tests/py/test_teams.py | 5 ++++- www/teams/create.json.spt | 3 ++- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/gratipay/models/team.py b/gratipay/models/team.py index 2917f9c7e2..27ca442384 100644 --- a/gratipay/models/team.py +++ b/gratipay/models/team.py @@ -71,8 +71,9 @@ def insert(cls, owner, **fields): """, fields) - def generate_review_url(self): - + def create_github_review_issue(self): + """POST to GitHub, and return the URL of the new issue. + """ api_url = "https://api.github.com/repos/{}/issues".format(self.review_repo) data = json.dumps({ "title": "review {}".format(self.name) , "body": "https://gratipay.com/{}/".format(self.slug) @@ -82,15 +83,12 @@ def generate_review_url(self): print(r.status_code) print(r.text) raise Heck - - review_url = r.json()['html_url'] - return self.update_review_url(review_url) + return r.json()['html_url'] - def update_review_url(self, review_url): + def set_review_url(self, review_url): self.db.run("UPDATE teams SET review_url=%s WHERE id=%s", (review_url, self.id)) self.set_attributes(review_url=review_url) - return review_url def get_og_title(self): diff --git a/tests/py/test_teams.py b/tests/py/test_teams.py index ff1a6f4e55..ddca695b61 100644 --- a/tests/py/test_teams.py +++ b/tests/py/test_teams.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import json +import mock import pytest from decimal import Decimal @@ -45,7 +46,9 @@ def test_can_construct_from_id(self): assert team.name == 'The Enterprise' assert team.owner == 'picard' - def test_can_create_new_team(self): + @mock.patch('gratipay.models.team.Team.create_github_review_issue') + def test_can_create_new_team(self, cgri): + cgri.return_value = REVIEW_URL self.make_participant('alice', claimed_time='now', email_address='', last_paypal_result='') r = self.post_new(dict(self.valid_data)) team = self.db.one("SELECT * FROM teams") diff --git a/www/teams/create.json.spt b/www/teams/create.json.spt index adbc14c637..48432f112c 100644 --- a/www/teams/create.json.spt +++ b/www/teams/create.json.spt @@ -58,7 +58,8 @@ if request.method == 'POST': except IntegrityError: raise Response(400, _("Sorry, there is already a team using '{}'.", fields['slug'])) - review_url = team.generate_review_url() + review_url = team.create_github_review_issue() + team.set_review_url(review_url) [---] application/json via json_dump {'review_url': review_url} From e7c9b40079d5057582dd5a62308e8fc145bfbf9b Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Thu, 2 Jul 2015 19:53:19 -0400 Subject: [PATCH 12/19] Put repo in default config This should help the test suite locate the right fixture. --- defaults.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/defaults.env b/defaults.env index 1aec797ea6..15e6102a77 100644 --- a/defaults.env +++ b/defaults.env @@ -76,7 +76,7 @@ GUNICORN_OPTS="--workers=1 --timeout=99999999" MANDRILL_KEY=Phh_Lm3RdPT5blqOPY4dVQ -REVIEW_REPO= +REVIEW_REPO=gratipay/review REVIEW_USERNAME= REVIEW_TOKEN= From 9b67d6e595cf3412f195f0b133bd1c5e0d5d754a Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Mon, 7 Sep 2015 22:56:49 -0400 Subject: [PATCH 13/19] Tweak the envvar names The name of the repo has settled down. It's `team-review`. Let's use envvars to match. --- defaults.env | 8 +++++--- gratipay/wireup.py | 10 +++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/defaults.env b/defaults.env index 15e6102a77..78405f009b 100644 --- a/defaults.env +++ b/defaults.env @@ -76,8 +76,10 @@ GUNICORN_OPTS="--workers=1 --timeout=99999999" MANDRILL_KEY=Phh_Lm3RdPT5blqOPY4dVQ -REVIEW_REPO=gratipay/review -REVIEW_USERNAME= -REVIEW_TOKEN= +# For testing Team review ticket posting +# Set your own username and an access token in local.env +TEAM_REVIEW_REPO=gratipay/test-gremlin +TEAM_REVIEW_USERNAME= +TEAM_REVIEW_TOKEN= RAISE_SIGNIN_NOTIFICATIONS=no diff --git a/gratipay/wireup.py b/gratipay/wireup.py index d4076a645b..798fa05048 100644 --- a/gratipay/wireup.py +++ b/gratipay/wireup.py @@ -86,8 +86,8 @@ def billing(env): def team_review(env): - Team.review_repo = env.review_repo - Team.review_auth = (env.review_username, env.review_token) + Team.review_repo = env.team_review_repo + Team.review_auth = (env.team_review_username, env.team_review_token) def username_restrictions(website): @@ -408,9 +408,9 @@ def env(): LOG_METRICS = is_yesish, INCLUDE_PIWIK = is_yesish, MANDRILL_KEY = unicode, - REVIEW_REPO = unicode, - REVIEW_USERNAME = unicode, - REVIEW_TOKEN = unicode, + TEAM_REVIEW_REPO = unicode, + TEAM_REVIEW_USERNAME = unicode, + TEAM_REVIEW_TOKEN = unicode, RAISE_SIGNIN_NOTIFICATIONS = is_yesish, # This is used in our Procfile. (PORT is also used but is provided by From abb97450a99688224f087192970ddfbf515ed173 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Mon, 7 Sep 2015 23:22:34 -0400 Subject: [PATCH 14/19] Show a success message after posting Also links to review tickets from homepage --- js/gratipay/new_team.js | 8 ++++---- scss/pages/homepage.scss | 10 +++++----- www/index.html.spt | 6 ++++-- www/new.spt | 8 ++++++++ 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/js/gratipay/new_team.js b/js/gratipay/new_team.js index 11b9bd3389..ab48061108 100644 --- a/js/gratipay/new_team.js +++ b/js/gratipay/new_team.js @@ -21,10 +21,10 @@ Gratipay.new_team.submitForm = function (e) { data: data, dataType: 'json', success: function (d) { - $('form').html( "

Thank you! We will follow up shortly with an email to " - + d.email + ". Please email " - + "us with any questions.

" - ) + $('a.review_url').attr('href', d.review_url).text(d.review_url); + $('form').slideUp(500, function() { + $('.application-complete').slideDown(250); + }); }, error: [Gratipay.error, function () { $input.prop('disable', false); }] }); diff --git a/scss/pages/homepage.scss b/scss/pages/homepage.scss index 78f1470d63..52939e1026 100644 --- a/scss/pages/homepage.scss +++ b/scss/pages/homepage.scss @@ -44,6 +44,11 @@ border: 1px solid $light-brown; border-style: solid none; + a { + color: $medium-gray; + position: relative; + z-index: 2; + } a:hover { background: none !important; color: $green !important; @@ -65,11 +70,6 @@ min-height: 48px; padding: 30px 64px 0 0; left: 0; - .owner a { - color: $medium-gray; - position: relative; - z-index: 2; - } span { white-space: nowrap; } diff --git a/www/index.html.spt b/www/index.html.spt index a857b497ce..6bed8fdc42 100644 --- a/www/index.html.spt +++ b/www/index.html.spt @@ -104,8 +104,10 @@ suppress_welcome = 'suppress-welcome' in request.cookie
- {{ status_icons[team.status]|safe }} - {{ i18ned_statuses[team.status] }} + + {{ status_icons[team.status]|safe }} + {{ i18ned_statuses[team.status] }} + · {{ _("created {ago}", ago=to_age(team.ctime, add_direction=True)) }} diff --git a/www/new.spt b/www/new.spt index b4ddebaf53..5f62619fbb 100644 --- a/www/new.spt +++ b/www/new.spt @@ -44,6 +44,14 @@ still_migrating = delta > 0 height: 200px; } +
From d18a271d56015f2b0c336b0bd8219ed8b7f598a0 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Mon, 7 Sep 2015 23:30:18 -0400 Subject: [PATCH 15/19] Update test so it works locally --- tests/py/fixtures/TestTeams.yml | 77 +++++++++++++++++++++++++++++++++ tests/py/test_teams.py | 2 +- 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 tests/py/fixtures/TestTeams.yml diff --git a/tests/py/fixtures/TestTeams.yml b/tests/py/fixtures/TestTeams.yml new file mode 100644 index 0000000000..9176a0870c --- /dev/null +++ b/tests/py/fixtures/TestTeams.yml @@ -0,0 +1,77 @@ +interactions: +- request: + body: "{\n \"body\": \"https://gratipay.com/gratiteam/\",\n \"title\": \"review + Gratiteam\"\n}" + headers: {} + method: POST + uri: https://api.github.com:443/repos/gratipay/test-gremlin/issues + response: + body: {string: !!python/unicode '{"url":"https://api.github.com/repos/gratipay/test-gremlin/issues/9","labels_url":"https://api.github.com/repos/gratipay/test-gremlin/issues/9/labels{/name}","comments_url":"https://api.github.com/repos/gratipay/test-gremlin/issues/9/comments","events_url":"https://api.github.com/repos/gratipay/test-gremlin/issues/9/events","html_url":"https://github.com/gratipay/test-gremlin/issues/9","id":105293440,"number":9,"title":"review + Gratiteam","user":{"login":"lgtest","id":1775515,"avatar_url":"https://avatars.githubusercontent.com/u/1775515?v=3","gravatar_id":"","url":"https://api.github.com/users/lgtest","html_url":"https://github.com/lgtest","followers_url":"https://api.github.com/users/lgtest/followers","following_url":"https://api.github.com/users/lgtest/following{/other_user}","gists_url":"https://api.github.com/users/lgtest/gists{/gist_id}","starred_url":"https://api.github.com/users/lgtest/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lgtest/subscriptions","organizations_url":"https://api.github.com/users/lgtest/orgs","repos_url":"https://api.github.com/users/lgtest/repos","events_url":"https://api.github.com/users/lgtest/events{/privacy}","received_events_url":"https://api.github.com/users/lgtest/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":0,"created_at":"2015-09-08T03:26:30Z","updated_at":"2015-09-08T03:26:30Z","closed_at":null,"body":"https://gratipay.com/gratiteam/","closed_by":null}'} + headers: + access-control-allow-credentials: ['true'] + access-control-allow-origin: ['*'] + access-control-expose-headers: ['ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, + X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, + X-Poll-Interval'] + cache-control: ['private, max-age=60, s-maxage=60'] + content-length: ['1533'] + content-security-policy: [default-src 'none'] + content-type: [application/json; charset=utf-8] + etag: ['"fda883470b69e8f697fe0644a8b3e5dc"'] + location: ['https://api.github.com/repos/gratipay/test-gremlin/issues/9'] + status: [201 Created] + strict-transport-security: [max-age=31536000; includeSubdomains; preload] + vary: ['Accept, Authorization, Cookie, X-GitHub-OTP', Accept-Encoding] + status: {code: 201, message: Created} +- request: + body: "{\n \"body\": \"https://gratipay.com/gratiteam/\",\n \"title\": \"review + Gratiteam\"\n}" + headers: {} + method: POST + uri: https://api.github.com:443/repos/gratipay/test-gremlin/issues + response: + body: {string: !!python/unicode '{"url":"https://api.github.com/repos/gratipay/test-gremlin/issues/10","labels_url":"https://api.github.com/repos/gratipay/test-gremlin/issues/10/labels{/name}","comments_url":"https://api.github.com/repos/gratipay/test-gremlin/issues/10/comments","events_url":"https://api.github.com/repos/gratipay/test-gremlin/issues/10/events","html_url":"https://github.com/gratipay/test-gremlin/issues/10","id":105293441,"number":10,"title":"review + Gratiteam","user":{"login":"lgtest","id":1775515,"avatar_url":"https://avatars.githubusercontent.com/u/1775515?v=3","gravatar_id":"","url":"https://api.github.com/users/lgtest","html_url":"https://github.com/lgtest","followers_url":"https://api.github.com/users/lgtest/followers","following_url":"https://api.github.com/users/lgtest/following{/other_user}","gists_url":"https://api.github.com/users/lgtest/gists{/gist_id}","starred_url":"https://api.github.com/users/lgtest/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lgtest/subscriptions","organizations_url":"https://api.github.com/users/lgtest/orgs","repos_url":"https://api.github.com/users/lgtest/repos","events_url":"https://api.github.com/users/lgtest/events{/privacy}","received_events_url":"https://api.github.com/users/lgtest/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":0,"created_at":"2015-09-08T03:26:30Z","updated_at":"2015-09-08T03:26:30Z","closed_at":null,"body":"https://gratipay.com/gratiteam/","closed_by":null}'} + headers: + access-control-allow-credentials: ['true'] + access-control-allow-origin: ['*'] + access-control-expose-headers: ['ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, + X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, + X-Poll-Interval'] + cache-control: ['private, max-age=60, s-maxage=60'] + content-length: ['1539'] + content-security-policy: [default-src 'none'] + content-type: [application/json; charset=utf-8] + etag: ['"16006dd40973a34b57eaa9c47f389e18"'] + location: ['https://api.github.com/repos/gratipay/test-gremlin/issues/10'] + status: [201 Created] + strict-transport-security: [max-age=31536000; includeSubdomains; preload] + vary: ['Accept, Authorization, Cookie, X-GitHub-OTP', Accept-Encoding] + status: {code: 201, message: Created} +- request: + body: "{\n \"body\": \"https://gratipay.com/gratiteam/\",\n \"title\": \"review + Gratiteam\"\n}" + headers: {} + method: POST + uri: https://api.github.com:443/repos/gratipay/test-gremlin/issues + response: + body: {string: !!python/unicode '{"url":"https://api.github.com/repos/gratipay/test-gremlin/issues/11","labels_url":"https://api.github.com/repos/gratipay/test-gremlin/issues/11/labels{/name}","comments_url":"https://api.github.com/repos/gratipay/test-gremlin/issues/11/comments","events_url":"https://api.github.com/repos/gratipay/test-gremlin/issues/11/events","html_url":"https://github.com/gratipay/test-gremlin/issues/11","id":105293442,"number":11,"title":"review + Gratiteam","user":{"login":"lgtest","id":1775515,"avatar_url":"https://avatars.githubusercontent.com/u/1775515?v=3","gravatar_id":"","url":"https://api.github.com/users/lgtest","html_url":"https://github.com/lgtest","followers_url":"https://api.github.com/users/lgtest/followers","following_url":"https://api.github.com/users/lgtest/following{/other_user}","gists_url":"https://api.github.com/users/lgtest/gists{/gist_id}","starred_url":"https://api.github.com/users/lgtest/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lgtest/subscriptions","organizations_url":"https://api.github.com/users/lgtest/orgs","repos_url":"https://api.github.com/users/lgtest/repos","events_url":"https://api.github.com/users/lgtest/events{/privacy}","received_events_url":"https://api.github.com/users/lgtest/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":0,"created_at":"2015-09-08T03:26:31Z","updated_at":"2015-09-08T03:26:31Z","closed_at":null,"body":"https://gratipay.com/gratiteam/","closed_by":null}'} + headers: + access-control-allow-credentials: ['true'] + access-control-allow-origin: ['*'] + access-control-expose-headers: ['ETag, Link, X-GitHub-OTP, X-RateLimit-Limit, + X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, + X-Poll-Interval'] + cache-control: ['private, max-age=60, s-maxage=60'] + content-length: ['1539'] + content-security-policy: [default-src 'none'] + content-type: [application/json; charset=utf-8] + etag: ['"f36a625d0f49b0e6555ec814383b5663"'] + location: ['https://api.github.com/repos/gratipay/test-gremlin/issues/11'] + status: [201 Created] + strict-transport-security: [max-age=31536000; includeSubdomains; preload] + vary: ['Accept, Authorization, Cookie, X-GitHub-OTP', Accept-Encoding] + status: {code: 201, message: Created} +version: 1 diff --git a/tests/py/test_teams.py b/tests/py/test_teams.py index ddca695b61..06d5111719 100644 --- a/tests/py/test_teams.py +++ b/tests/py/test_teams.py @@ -9,7 +9,7 @@ from gratipay.models.team import Team, AlreadyMigrated -REVIEW_URL = "https://github.com/gratipay/review/issues/2" +REVIEW_URL = "https://github.com/gratipay/test-gremlin/issues/9" class TestTeams(Harness): From aafa030bd2a926a77ab208c40222e96a2da98320 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Mon, 7 Sep 2015 23:39:28 -0400 Subject: [PATCH 16/19] Fix pyflakes --- gratipay/models/team.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/gratipay/models/team.py b/gratipay/models/team.py index 27ca442384..b9d5a346b6 100644 --- a/gratipay/models/team.py +++ b/gratipay/models/team.py @@ -2,7 +2,7 @@ """ import requests from postgres.orm import Model -from aspen import json +from aspen import json, log status_icons = { "unreviewed": "✋" , "rejected": "❌" @@ -79,11 +79,13 @@ def create_github_review_issue(self): , "body": "https://gratipay.com/{}/".format(self.slug) }) r = requests.post(api_url, auth=self.review_auth, data=data) - if r.status_code != 201: - print(r.status_code) - print(r.text) - raise Heck - return r.json()['html_url'] + if r.status_code == 201: + out = r.json()['html_url'] + else: + log(r.status_code) + log(r.text) + out = "https://github.com/gratipay/team-review/issues#error-{}".format(r.status_code) + return out def set_review_url(self, review_url): From 94536fa2c921dffebb37fa73fd42f40dc59a4a8f Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Mon, 7 Sep 2015 23:39:38 -0400 Subject: [PATCH 17/19] Link to review ticket from Team page --- www/%team/index.html.spt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/www/%team/index.html.spt b/www/%team/index.html.spt index d6bbdfc65d..775360d52b 100644 --- a/www/%team/index.html.spt +++ b/www/%team/index.html.spt @@ -86,11 +86,15 @@ suppress_sidebar = not(team.is_approved or user.ADMIN) {% block content %}

- {% if team.status == 'unreviewed' %} - {{ _("Under Review") }} | - {% elif team.status == 'rejected' %} - {{ _("Rejected") }} | - {% endif %} + + {% if team.status == 'approved' %} + {{ _("Approved") }} + {% elif team.status == 'unreviewed' %} + {{ _("Under Review") }} + {% elif team.status == 'rejected' %} + {{ _("Rejected") }} + {% endif %} + | {{ _("Homepage") }} From f037c2e717a05d48846fe50779361a69e87c67a8 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Tue, 8 Sep 2015 00:03:59 -0400 Subject: [PATCH 18/19] Fix email display in success message --- www/new.spt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/new.spt b/www/new.spt index 5f62619fbb..c29b57240b 100644 --- a/www/new.spt +++ b/www/new.spt @@ -48,7 +48,7 @@ still_migrating = delta > 0

{{ _("Thanks! Your public review ticket is:") }}

{{ _( "You can track and participate in our review process there. We will send a notification to {email} when we finish our review." - , email=user.participant.email + , email=user.participant.email_address ) }}

{{ _("Thanks for applying!") }}

From 7acba4826ae29219b8d9aa3432ebc52c8edbf194 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Tue, 8 Sep 2015 00:11:00 -0400 Subject: [PATCH 19/19] Send email notifications for approval/rejection --- emails/team-approved.spt | 18 ++++++++++++++++++ emails/team-rejected.spt | 13 +++++++++++++ www/%team/set-status.json.spt | 5 +++++ 3 files changed, 36 insertions(+) create mode 100644 emails/team-approved.spt create mode 100644 emails/team-rejected.spt diff --git a/emails/team-approved.spt b/emails/team-approved.spt new file mode 100644 index 0000000000..8f6dab84ab --- /dev/null +++ b/emails/team-approved.spt @@ -0,0 +1,18 @@ +{{ _("Team Application Approved!") }} +[---] text/html +{{ _( "We've approved your application for the '{team}' Team on Gratipay. For details, please refer to {a}our review ticket{_a}." + , team=team.name + , a=(''|safe).format(team.review_url) + , _a=''|safe + ) }} +
+
+{{ _("Thanks for using Gratipay! :-)") }} +[---] text/plain +{{ _( "We've approved your application for the '{team}' Team on Gratipay. For details, please refer to our review ticket:" + , team=team.name + ) }} + +{{ team.review_url }} + +{{ _("Thanks for using Gratipay! :-)") }} diff --git a/emails/team-rejected.spt b/emails/team-rejected.spt new file mode 100644 index 0000000000..f0b0d7b7aa --- /dev/null +++ b/emails/team-rejected.spt @@ -0,0 +1,13 @@ +{{ _("Team Application Rejected") }} +[---] text/html +{{ _( "We've rejected your application for the '{team}' Team on Gratipay. For details, please refer to {a}our review ticket{_a}." + , team=team.name + , a=(''|safe).format(team.review_url) + , _a=''|safe + ) }} +[---] text/plain +{{ _( "We've rejected your application for the '{team}' Team on Gratipay. For details, please refer to our review ticket:" + , team=team.name + ) }} + +{{ team.review_url }} diff --git a/www/%team/set-status.json.spt b/www/%team/set-status.json.spt index ba6fe7ca24..ee8bed3a6f 100644 --- a/www/%team/set-status.json.spt +++ b/www/%team/set-status.json.spt @@ -2,6 +2,7 @@ from aspen import Response from gratipay.models import add_event from gratipay.models.team import Team +from gratipay.models.participant import Participant [---] if not user.ADMIN: raise Response(403) @@ -36,5 +37,9 @@ with website.db.get_cursor() as c: action='set', values=dict(status=status) )) + if status in ('rejected', 'approved'): + owner = Participant.from_username(team.owner) + owner.send_email('team-'+status, team=team, include_unsubscribe=False) + [---] application/json via json_dump {"status": status}