diff --git a/adsws/feedback/app.py b/adsws/feedback/app.py
index 407a7ba..30a136b 100644
--- a/adsws/feedback/app.py
+++ b/adsws/feedback/app.py
@@ -5,7 +5,7 @@
from .. import factory
from flask.ext.restful import Api
from flask.ext.cors import CORS
-from adsws.feedback.views import SlackFeedback
+from adsws.feedback.views import UserFeedback
from flask.ext.mail import Mail
@@ -35,6 +35,7 @@ def create_app(**kwargs_config):
)
# Add end points
- api.add_resource(SlackFeedback, '/slack')
+ api.add_resource(UserFeedback, '/userfeedback')
+
return app
diff --git a/adsws/feedback/config.py b/adsws/feedback/config.py
index 3d18912..b76e7f2 100644
--- a/adsws/feedback/config.py
+++ b/adsws/feedback/config.py
@@ -12,9 +12,27 @@
FEEDBACK_SLACK_END_POINT = 'https://hooks.slack.com/services/TOKEN/TOKEN'
FEEDBACK_SLACK_EMOJI = ':interrobang:'
-FEEDBACK_EMAIL = 'adshelp@cfa.harvard.edu'
+FORM_SLACK_EMOJI = ':inbox_tray:'
+DEFAULT_EMAIL = 'adshelp@cfa.harvard.edu'
+# Feedback processing depends on 'origin' attribute supplied in POST data
+FEEDBACK_FORMS_ORIGIN = 'user_submission'
+BBB_FEEDBACK_ORIGIN = 'bbb_feedback'
+# Email template to be applied based on email subject
+FEEDBACK_TEMPLATES = {
+ 'Missing References': 'missing_references.txt',
+ 'Associated Articles': 'associated_articles.txt',
+ 'Updated Record': 'updated_record.txt',
+ 'New Record': 'new_record.txt',
+ 'Bumblebee Feedback':'bumblebee_feedback.txt'
+}
+# Override defaul recipient based on email subject (key)
+FEEDBACK_EMAILS = {
+ 'Missing References': 'ads@cfa.harvard.edu',
+}
+
GOOGLE_RECAPTCHA_ENDPOINT = 'https://www.google.com/recaptcha/api/siteverify'
GOOGLE_RECAPTCHA_PRIVATE_KEY = 'MY_PRIVATE_KEY'
CORS_DOMAINS = ['https://ui.adsabs.harvard.edu']
CORS_HEADERS = []
CORS_METHODS = ['POST', 'GET']
+
diff --git a/adsws/feedback/templates/associated_articles.txt b/adsws/feedback/templates/associated_articles.txt
new file mode 100644
index 0000000..1e33d6f
--- /dev/null
+++ b/adsws/feedback/templates/associated_articles.txt
@@ -0,0 +1,16 @@
+From: {{data.name}}
+Address: {{data.email}}
+
+Correlated articles:
+{% if 'other' == data.relationship -%}
+ {{data.custom_name}}: {{data.source}} {{data.target[0]}}
+{% else -%}
+ {{data.relationship}}: {{data.source}} {{data.target[0]}}
+ {% if data.target|length > 1 -%}
+ {% for bibcode in data.target[1:] -%}
+ {% set indent_count = data.relationship | length -%}
+ {{bibcode | indent(indent_count + 17, true)}}
+ {% endfor %}
+ {% endif %}
+{% endif %}
+
diff --git a/adsws/feedback/templates/bumblebee_feedback.txt b/adsws/feedback/templates/bumblebee_feedback.txt
new file mode 100644
index 0000000..de94e5b
--- /dev/null
+++ b/adsws/feedback/templates/bumblebee_feedback.txt
@@ -0,0 +1,3 @@
+{% for key, value in data.items() -%}
+ **{{ key }}** {{ value }}
+{% endfor %}
\ No newline at end of file
diff --git a/adsws/feedback/templates/missing_references.txt b/adsws/feedback/templates/missing_references.txt
new file mode 100644
index 0000000..2b47dd0
--- /dev/null
+++ b/adsws/feedback/templates/missing_references.txt
@@ -0,0 +1,7 @@
+From: {{data.name}}
+Address: {{data.email}}
+
+Missing references:
+{% for reference in data.references -%}
+{{reference.citing}}[tab]{{reference.cited}}[tab]{{reference.refstring}}
+{% endfor %}
diff --git a/adsws/feedback/templates/new_record.txt b/adsws/feedback/templates/new_record.txt
new file mode 100644
index 0000000..1fcbc63
--- /dev/null
+++ b/adsws/feedback/templates/new_record.txt
@@ -0,0 +1,13 @@
+From: {{data.name}}
+Address: {{data.email}}
+
+Abstract data for new record. Summary:
+
+Collection: {{data.new.collection[0]}}
+
+%R {{data.new.bibcode}}
+%T {{data.new.title}}
+%A {{data.new.author_list}}
+%D {{data.new.publicationDate}}
+%J {{data.new.publication}}
+%B {{data.new.abstract}}
\ No newline at end of file
diff --git a/adsws/feedback/templates/updated_record.txt b/adsws/feedback/templates/updated_record.txt
new file mode 100644
index 0000000..9adec08
--- /dev/null
+++ b/adsws/feedback/templates/updated_record.txt
@@ -0,0 +1,6 @@
+From: {{data.name}}
+Address: {{data.email}}
+
+Correction for {{data.original.bibcode}}:
+
+{{data.diff}}
\ No newline at end of file
diff --git a/adsws/feedback/utils.py b/adsws/feedback/utils.py
index 89192f2..fba1cb3 100644
--- a/adsws/feedback/utils.py
+++ b/adsws/feedback/utils.py
@@ -5,14 +5,20 @@
"""
from flask import current_app
from flask.ext.mail import Message
+import json
-def send_feedback_email(name, sender, feedback):
- help_email = current_app.config['FEEDBACK_EMAIL']
- msg = Message(subject="Bumblebee Feedback from %s (%s)" % (name, sender),
- recipients=[help_email],
- sender=("adshelp", help_email),
+def send_feedback_email(name, sender, subject, data, attachments=None):
+ # Allow the default recipient to be overriden depending on email subject
+ email = current_app.config['FEEDBACK_EMAILS'].get(subject, current_app.config['DEFAULT_EMAIL'])
+ msg = Message(subject="%s from %s (%s)" % (subject, name, sender),
+ recipients=[email],
+ sender=("ADS", email),
reply_to=(name, sender),
- body=feedback)
+ body=data)
+ if attachments:
+ for attachment in attachments:
+ # Each entry is a tuple of file name and JSON data
+ msg.attach(attachment[0], "application/json", json.dumps(attachment[1]))
current_app.extensions['mail'].send(msg)
return msg
diff --git a/adsws/feedback/views.py b/adsws/feedback/views.py
index 743e495..dedb0ea 100644
--- a/adsws/feedback/views.py
+++ b/adsws/feedback/views.py
@@ -5,13 +5,15 @@
import json
import requests
-from flask import current_app, request
+import copy
+from flask import current_app, request, render_template
from flask.ext.restful import Resource
from adsws.ext.ratelimiter import ratelimit, scope_func
from adsws.feedback.utils import err
from adsws.accounts.utils import verify_recaptcha, get_post_data
from werkzeug.exceptions import BadRequestKeyError
from utils import send_feedback_email
+from urllib import unquote
API_DOCS = 'https://github.com/adsabs/adsabs-dev-api'
ERROR_UNVERIFIED_CAPTCHA = dict(
@@ -23,77 +25,87 @@
.format(API_DOCS),
number=404
)
+ERROR_FEEDBACKFORM_PROBLEM = dict(
+ body='Error while processing feedback form data',
+ number=404
+)
+ERROR_EMAILBODY_PROBLEM = dict(
+ body='Unable to generate email body',
+ number=404
+)
+ERROR_UNKNOWN_ORIGIN = dict(
+ body='No origin provided in feedback data',
+ number=404
+)
ERROR_WRONG_ENDPOINT = dict(
body='Re-directed due to malformed request or incorrect end point',
number=302
)
+ERROR_EMAIL_NOT_SENT = dict(
+ body='Delivery of feedback email to ADS failed!',
+ number=404
+)
-class SlackFeedback(Resource):
+class UserFeedback(Resource):
"""
- Forwards a user's feedback to slack chat using a web end
+ Forwards a user's feedback to Slack and/or email
"""
decorators = [ratelimit.shared_limit_and_check("500/600 second", scope=scope_func)]
@staticmethod
- def prettify_post(post_data):
+ def create_email_body(post_data):
"""
- Converts the given input into a prettified version
- :param post_data: the post data to prettify, dictionary expected
- :return: prettified_post data, dictionary
+ Takes the data from the feedback and fills out the appropriate template
+ :param post_data: the post data to fill out email template, dictionary expected
+ :return: email body, string
"""
- channel = post_data.get('channel', '#feedback')
- username = post_data.get('username', 'TownCrier')
-
- name = post_data.get('name', 'TownCrier')
- reply_to = post_data.get('_replyto', 'TownCrier@lonelyvilla.ge')
-
- try:
- comments = post_data['comments']
- except BadRequestKeyError:
- raise
-
- text = [
- '*Commenter*: {}'.format(name),
- '*e-mail*: {}'.format(reply_to),
- '*Feedback*: {}'.format(comments.encode('utf-8')),
- ]
-
- used = ['channel', 'username', 'name', '_replyto', 'comments', 'g-recaptcha-response']
- for key in post_data:
- if key in used:
- continue
- text.append('*{}*: {}'.format(key, post_data[key]))
- text = '\n'.join(text)
-
- feedback_email = 'no email sent'
- if post_data.has_key('_replyto') and post_data.has_key('name'):
+ # We will be manipulating the dictionary with POST data, so make a copy
+ email_data = copy.copy(post_data)
+ # Determine the origin of the feedback. There are some origin-specific actions
+ origin = post_data.get('origin', 'NA')
+ if origin == current_app.config['BBB_FEEDBACK_ORIGIN']:
try:
- res = send_feedback_email(name, reply_to, text)
- feedback_email = 'success'
- except Exception as e:
- current_app.logger.info('Sending feedback mail failed: %s' % str(e))
- feedback_email = 'failed'
-
- text = '```Incoming Feedback```\n' + text + '\n*sent to adshelp*: {}\n'.format(feedback_email)
-
- icon_emoji = current_app.config['FEEDBACK_SLACK_EMOJI']
- prettified_data = {
- 'text': text,
- 'username': username,
- 'channel': channel,
- 'icon_emoji': icon_emoji
- }
- return prettified_data
-
+ comments = email_data['comments']
+ except BadRequestKeyError:
+ raise
+ email_data['_subject'] = 'Bumblebee Feedback'
+ email_data['comments'] = post_data['comments'].encode('utf-8')
+ used = ['channel', 'username', 'name', '_replyto', 'g-recaptcha-response']
+ for key in used:
+ email_data.pop(key, None)
+ # Retrieve the appropriate template
+ template = current_app.config['FEEDBACK_TEMPLATES'].get(email_data.get('_subject'))
+ # For abstract corrections, the POST payload has a "diff" attribute that contains
+ # the updated fields in Github "diff" format, URL encoded. For display purposes,
+ # this needs to be decoded.
+ if post_data.has_key('diff'):
+ email_data['diff'] = unquote(post_data['diff'])
+ # In the case of a new record the mail body will show a summary
+ # In this summary it's easier to show a author list in the form of a string
+ # We also attach the JSON data of the new record as a file
+ if post_data.get('_subject') == 'New Record':
+ try:
+ email_data['new']['author_list'] = ";".join([a['name'] for a in post_data['new']['authors']])
+ except:
+ email_data['new']['author_list'] = ""
+ # Construct the email body
+ body = render_template(template, data=email_data)
+ # If there is a way to insert tabs in the template, it should happen there
+ # (currently, this only happens in the missing_references.txt template)
+ body = body.replace('[tab]','\t')
+
+ return body
+
def post(self):
"""
HTTP POST request
- :return: status code from the slack end point
+ :return: status code from the slack end point and for sending user feedback emails
"""
post_data = get_post_data(request)
- current_app.logger.info('Received feedback: {0}'.format(post_data))
+
+ current_app.logger.info('Received feedback of type {0}: {1}'.format(post_data.get('_subject'), post_data))
if not post_data.get('g-recaptcha-response', False) or \
not verify_recaptcha(request):
@@ -101,35 +113,97 @@ def post(self):
return err(ERROR_UNVERIFIED_CAPTCHA)
else:
current_app.logger.info('Skipped captcha!')
-
+ # We only allow POST data from certain origins
+ allowed_origins = [v for k,v in current_app.config.items() if k.endswith('_ORIGIN')]
+ origin = post_data.get('origin', 'NA')
+ if origin == 'NA' or origin not in allowed_origins:
+ return err(ERROR_UNKNOWN_ORIGIN)
+ # Some variable definitions
+ email_body = ''
+ slack_data = ''
+ attachments=[]
+ # Generate the email body based on the data in the POST payload
try:
- current_app.logger.info('Prettifiying post data: {0}'
- .format(post_data))
- formatted_post_data = json.dumps(self.prettify_post(post_data))
- current_app.logger.info('Data prettified: {0}'
- .format(formatted_post_data))
+ email_body = self.create_email_body(post_data)
except BadRequestKeyError as error:
current_app.logger.error('Missing keywords: {0}, {1}'
.format(error, post_data))
return err(ERROR_MISSING_KEYWORDS)
-
- try:
- slack_response = requests.post(
- url=current_app.config['FEEDBACK_SLACK_END_POINT'],
- data=formatted_post_data,
- timeout=60
- )
- except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
- return b'504 Gateway Timeout', 504
- current_app.logger.info('slack response: {0}'
- .format(slack_response.status_code))
-
- # Slack annoyingly redirects if you have the wrong end point
- current_app.logger.info('Slack API' in slack_response.text)
-
- if 'Slack API' in slack_response.text:
- return err(ERROR_WRONG_ENDPOINT)
- elif slack_response.status_code == 200:
- return {}, 200
- else:
- return {'msg': 'Unknown error'}, slack_response.status_code
+ except Exception as error:
+ current_app.logger.error('Fatal error creating email body: {0}'.format(error))
+ return err(ERROR_EMAILBODY_PROBLEM)
+ # Retrieve the name of the person submitting the feedback
+ name = post_data.get('name', 'TownCrier')
+ # There are some origin-specific actions
+ if origin == current_app.config['FEEDBACK_FORMS_ORIGIN']:
+ # The reply_to for feedback form data
+ reply_to = post_data.get('email')
+ # In the case of new or corrected records, attachments are sent along
+ if post_data.get('_subject') == 'New Record':
+ attachments.append(('new_record.json', post_data['new']))
+ if post_data.get('_subject') == 'Updated Record':
+ attachments.append(('updated_record.json', post_data['new']))
+ attachments.append(('original_record.json', post_data['original']))
+ # Prepare a minimal Slack message
+ channel = post_data.get('channel', '#feedback')
+ username = post_data.get('username', 'TownCrier')
+ icon_emoji = current_app.config['FORM_SLACK_EMOJI']
+ text = 'Received data from feedback form "{0}" from {1} ({2})'.format(post_data.get('_subject'), post_data.get('name'), post_data.get('email'))
+ slack_data = {
+ 'text': text,
+ 'username': username,
+ 'channel': channel,
+ 'icon_emoji': icon_emoji
+ }
+ elif origin == current_app.config['BBB_FEEDBACK_ORIGIN']:
+ # The reply_to for the general feedback data
+ reply_to = post_data.get('_replyto', 'TownCrier@lonelyvilla.ge')
+ # Prepare the Slack message with submitted data
+ text = '```Incoming Feedback```\n' + email_body
+ channel = post_data.get('channel', '#feedback')
+ username = post_data.get('username', 'TownCrier')
+ icon_emoji = current_app.config['FEEDBACK_SLACK_EMOJI']
+ slack_data = {
+ 'text': text,
+ 'username': username,
+ 'channel': channel,
+ 'icon_emoji': icon_emoji
+ }
+ # If we have an email body (should always be the case), send out the email
+ if email_body:
+ email_sent = False
+ try:
+ res = send_feedback_email(name, reply_to, post_data['_subject'], email_body, attachments=attachments)
+ email_sent = True
+ except Exception as e:
+ current_app.logger.error('Fatal error while processing feedback form data: {0}'.format(e))
+ email_sent = False
+ if not email_sent:
+ # If the email could not be sent, we can still log the data submitted
+ current_app.logger.error('Sending of email failed. Feedback data submitted by {0} ({1}): {2}'.format(post_data, name, post_data.get('email')))
+ return err(ERROR_EMAIL_NOT_SENT)
+ # If we have Slack data, post the message to Slack
+ if slack_data:
+ slack_data['text'] += '\n*sent to adshelp*: {0}'.format(email_sent)
+ try:
+ slack_response = requests.post(
+ url=current_app.config['FEEDBACK_SLACK_END_POINT'],
+ data=json.dumps(slack_data),
+ timeout=60
+ )
+ except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
+ return b'504 Gateway Timeout', 504
+ current_app.logger.info('slack response: {0}'
+ .format(slack_response.status_code))
+
+ # Slack annoyingly redirects if you have the wrong end point
+ current_app.logger.info('Slack API' in slack_response.text)
+
+ if 'Slack API' in slack_response.text:
+ return err(ERROR_WRONG_ENDPOINT)
+ elif slack_response.status_code == 200:
+ return {}, 200
+ else:
+ return {'msg': 'Unknown error'}, slack_response.status_code
+
+ return {}, 200
diff --git a/adsws/tests/stubdata/__init__.py b/adsws/tests/stubdata/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/adsws/tests/stubdata/associated_errata.py b/adsws/tests/stubdata/associated_errata.py
new file mode 100644
index 0000000..0c415c7
--- /dev/null
+++ b/adsws/tests/stubdata/associated_errata.py
@@ -0,0 +1,13 @@
+data = {
+ "origin": "user_submission",
+ "g-recaptcha-response": "correct_response",
+ "_subject": "Associated Articles",
+ "name": "Tim Hostetler",
+ "email": "twhostetler0@gmail.com",
+ "source": "2014AJ....147..124M",
+ "target": [
+ "2020PDU....3000620S",
+ "1999ApJ...511L..65Y"
+ ],
+ "relationship": "errata"
+}
\ No newline at end of file
diff --git a/adsws/tests/stubdata/associated_other.py b/adsws/tests/stubdata/associated_other.py
new file mode 100644
index 0000000..e7ef1ae
--- /dev/null
+++ b/adsws/tests/stubdata/associated_other.py
@@ -0,0 +1,16 @@
+data = {
+ "origin": "user_submission",
+ "g-recaptcha-response": "correct_response",
+ "_subject": "Associated Articles",
+ "name": "Tim Hostetler",
+ "email": "twhostetler0@gmail.com",
+ "source": "2014AJ....147..124M",
+ "target": [
+ "2020PDU....3000620S",
+ "1999ApJ...511L..65Y"
+ ],
+ "relationship": "other",
+ "custom_name": "TEST TEST TEST"
+}
+
+response = 'From: Tim Hostetler\nAddress: twhostetler0@gmail.com\n\nCorrelated articles:\nTEST TEST TEST: 2014AJ....147..124M 2020PDU....3000620S\n\n'
\ No newline at end of file
diff --git a/adsws/tests/stubdata/corrected_abstract.py b/adsws/tests/stubdata/corrected_abstract.py
new file mode 100644
index 0000000..1f1afa6
--- /dev/null
+++ b/adsws/tests/stubdata/corrected_abstract.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+data = {
+ "origin": "user_submission",
+ "g-recaptcha-response": "correct_response",
+ "_subject": "Updated Record",
+ "original": {
+ "comments": "test",
+ "recaptcha": "03AGdBq27Ni592wVKdm1Fk-nj_7pygAegrToYNuEGR3rUb8kMBx9lvwRmBpEc9qDVoqFlXpN9nDtNiIS4QwaUOPOmzPPg6_q74U9XDazRXDhGFMBYMh_a18EtMBaIXMGKU4YUAWSViQ5QEK8ExCZDaB_tSDRFzAgRlUddDUQSouwhtw7QYiR7NC0vWV8Yx2i43fBBEomI4UlJCyuSh7ZobS5MGC9JvgXW9O5uwjB6HLZ9znd0v_fAKXKJNmc-Kog7ZIIvEFXPJFPgdNbevm3vu1IxRx5SYAsztmYwK8sQ6K612IWXH7nRebF-2a5-yukXmpIh_S_AUigm8TqIjcX19lI_ZBNAlsjagH-wg2nSmPks6vva4AcPHaLllQURKsL-TNScnqWLJ0GpZ",
+ "name": "Tim Hostetler",
+ "email": "twhostetler0@gmail.com",
+ "entryType": "edit",
+ "bibcode": "2021NewA...8301464S",
+ "title": "Dynamical analysis with thermodynamic aspects of anisotropic dark energy bounce cosmological model in f(R, G) gravity",
+ "publication": "New Astronomy, Volume 83, article id. 101464.",
+ "publicationDate": "2021-02-00",
+ "abstract": "Present analysis dedicated to the dynamical investigation of anisotropic dark energy LRS Bianchi type-I cosmological model in the context of modified gravity in which Langrangian be the arbitrary function of Ricci scalar and Gouss-Bonnet invariant say f(R, G) gravity in the way of anisotropic fluid. The classification of the field equations towards f(R , G) =f0RmG 1 - m make available that the model is purely accelerating corresponds to 0 ≤ q ≤ - 1 . We govern the features of the derived cosmological model in view of the hybrid law inflation in bounce form (which involve power and de-Sitter cosmology) for the average scale factor. Also discussed the singularity of the model with the help of curvature of the model. It is observed that the model is fully engaged with both matter which exist initially for short expansion and dark energy dominated era and rests existing in quintessence dominated era and for sufficiently large time derived model forecasts that the anisotropy of the model will damp out and the Universe will turn out to be isotropic one and also observed that for quintessence dominated era the temperature and entropy density of anticipated model are positive definite along with some physical and kinematical parameters of the bounce model is also discussed in details.",
+ "authors": [
+ {
+ "id": "Shekh, S. H._0",
+ "position": 0,
+ "name": "Shekh, S. H.",
+ "aff": "Department of Mathematics, S. P. M. Science and Gilani Arts Commerece College Ghatanji, Maharashtra 445301, India",
+ "orcid": ""
+ }
+ ],
+ "keywords": ["95.36.+x", "04.50.kd", "98.80.Jk"],
+ "urls": [
+
+ ]
+ },
+ "new": {
+ "comments": "test",
+ "bibcode": "2021NewA...8301464S",
+ "title": "Dynamical analysis with thermodynamic aspects of anisotropic dark energy bounce cosmological model in f(R, G) gravity",
+ "publication": "New Astronomy, Volume 83, article id. 101464.",
+ "publicationDate": "2021-02-00",
+ "abstract": "Present analysis dedicated to the dynamical investigation of anisotropic dark energy LRS Bianchi type-I cosmological model in the context of modified gravity in which Langrangian be the arbitrary function of Ricci scalar and Gouss-Bonnet invariant say f(R, G) gravity in the way of anisotropic fluid. The classification of the field equations towards f(R , G) =f0RmG 1 - m make available that the model is purely accelerating corresponds to 0 ≤ q ≤ - 1 . We govern the features of the derived cosmological model in view of the hybrid law inflation in bounce form (which involve power and de-Sitter cosmology) for the average scale factor. Also discussed the singularity of the model with the help of curvature of the model. It is observed that the model is fully engaged with both matter which exist initially for short expansion and dark energy dominated era and rests existing in quintessence dominated era and for sufficiently large time derived model forecasts that the anisotropy of the model will damp out and the Universe will turn out to be isotropic one and also observed that for quintessence dominated era the temperature and entropy density of anticipated model are positive definite along with some physical and kinematical parameters of the bounce model is also discussed in details.",
+ "authors": [
+ {
+ "id": "Shekh, S. H._0",
+ "position": 0,
+ "name": "Shekh, S. H.",
+ "aff": "Department of Mathematics, S. P. M. Science and Gilani Arts Commerece College Ghatanji, Maharashtra 445301, India",
+ "orcid": ""
+ }
+ ],
+ "keywords": ["95.36.+x", "04.50.kd", "98.80.Jk"],
+ "urls": [
+
+ ],
+ "collection": [
+ "astronomy",
+ "physics"
+ ]
+ },
+ "name": "Tim Hostetler",
+ "email": "twhostetler0@gmail.com",
+ "diff": "%0A%20%20%3E%3E%3E%3E%20Title%0A%20%20test%0A%20%20%3C%3C%3C%3C%0A%0A%20%20%3E%3E%3E%3E%20Publication%0A%20%20test%0A%20%20%3C%3C%3C%3C%0A%0A%20%20%3E%3E%3E%3E%20PublicationDate%0A%20%202020-01%0A%20%20%3C%3C%3C%3C%0A%0A%20%20%3E%3E%3E%3E%20Comments%0A%20%20test%0A%20%20%3C%3C%3C%3C"
+}
+
+response = 'From: Tim Hostetler\nAddress: twhostetler0@gmail.com\n\nCorrection for 2021NewA...8301464S:\n\n\n >>>> Title\n test\n <<<<\n\n >>>> Publication\n test\n <<<<\n\n >>>> PublicationDate\n 2020-01\n <<<<\n\n >>>> Comments\n test\n <<<<'
\ No newline at end of file
diff --git a/adsws/tests/stubdata/general_feedback.py b/adsws/tests/stubdata/general_feedback.py
new file mode 100644
index 0000000..44e105b
--- /dev/null
+++ b/adsws/tests/stubdata/general_feedback.py
@@ -0,0 +1,3 @@
+data = {'origin':'bbb_feedback','engine': u'Gecko', 'g-recaptcha-response': u'correct_response', 'feedback-type': u'feedback', '_gotcha': u'', 'user-agent-string': u'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0', '_subject': u'Bumblebee Feedback', 'current_query': u'filter_property_fq_property=AND&filter_property_fq_property=property%3A%22refereed%22&fq=%7B!type%3Daqp%20v%3D%24fq_property%7D&fq_property=(property%3A%22refereed%22)&p_=0&q=year%3A2020%20%20full%3A%22cfht%22%20NOT%20docs(library%2FEyD7bWIGRNmxqCofTjNw4Q)%20NOT%20docs(library%2F49PdHlcMRh2_KF672dbcZw)&sort=date%20asc%2C%20bibcode%20asc', 'browser.name': u'Firefox', 'name': u'Happy User', 'url': u'https://ui.adsabs.harvard.edu/search/filter_property_fq_property=AND&filter_property_fq_property=property%3A%22refereed%22&fq=%7B!type%3Daqp%20v%3D%24fq_property%7D&fq_property=(property%3A%22refereed%22)&p_=0&q=year%3A2020%20%20full%3A%22cfht%22%20NOT%20docs(library%2FEyD7bWIGRNmxqCofTjNw4Q)%20NOT%20docs(library%2F49PdHlcMRh2_KF672dbcZw)&sort=date%20asc%2C%20bibcode%20asc', 'current_page': u'search-page', 'comments': u'Hi,\r\n\r\n Thank you for maintaining this awesome service. Can you please give me more citations?\r\n\r\nThanks in advance,\r\nHappy User', 'platform': u'desktop', '_replyto': u'happyuser@happydomain.universe', 'os': u'Linux', 'currentuser': u'devost@cfht.hawaii.edu', 'browser.version': u'80.0'}
+
+response = '**origin** bbb_feedback\n**engine** Gecko\n**feedback-type** feedback\n**_gotcha** \n**user-agent-string** Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\n**_subject** Bumblebee Feedback\n**current_query** filter_property_fq_property=AND&filter_property_fq_property=property%3A%22refereed%22&fq=%7B!type%3Daqp%20v%3D%24fq_property%7D&fq_property=(property%3A%22refereed%22)&p_=0&q=year%3A2020%20%20full%3A%22cfht%22%20NOT%20docs(library%2FEyD7bWIGRNmxqCofTjNw4Q)%20NOT%20docs(library%2F49PdHlcMRh2_KF672dbcZw)&sort=date%20asc%2C%20bibcode%20asc\n**url** https://ui.adsabs.harvard.edu/search/filter_property_fq_property=AND&filter_property_fq_property=property%3A%22refereed%22&fq=%7B!type%3Daqp%20v%3D%24fq_property%7D&fq_property=(property%3A%22refereed%22)&p_=0&q=year%3A2020%20%20full%3A%22cfht%22%20NOT%20docs(library%2FEyD7bWIGRNmxqCofTjNw4Q)%20NOT%20docs(library%2F49PdHlcMRh2_KF672dbcZw)&sort=date%20asc%2C%20bibcode%20asc\n**current_page** search-page\n**comments** Hi,\r\n\r\n Thank you for maintaining this awesome service. Can you please give me more citations?\r\n\r\nThanks in advance,\r\nHappy User\n**platform** desktop\n**browser.name** Firefox\n**os** Linux\n**currentuser** devost@cfht.hawaii.edu\n**browser.version** 80.0\n'
diff --git a/adsws/tests/stubdata/missing_references.py b/adsws/tests/stubdata/missing_references.py
new file mode 100644
index 0000000..221f6ad
--- /dev/null
+++ b/adsws/tests/stubdata/missing_references.py
@@ -0,0 +1,26 @@
+data = {
+ "origin": "user_submission",
+ "g-recaptcha-response": "correct_response",
+ "_subject": "Missing References",
+ "name": "Tim Hostetler",
+ "email": "twhostetler0@gmail.com",
+ "references": [
+ {
+ "citing": "2020arXiv200310949S",
+ "cited": "2021NewA...8301464S",
+ "refstring": "Shekh, S. H. (2021), New Astronomy, Volume 83, article id. 101464."
+ },
+ {
+ "citing": "2021NewA...8201452A",
+ "cited": "2020PDU....3000664A",
+ "refstring": "Arora, S., et al. (2020), Physics of the Dark Universe, Volume 30, article id. 100664."
+ },
+ {
+ "citing": "2020arXiv200310949S",
+ "cited": "2020PDU....3000620S",
+ "refstring": "Singh, K. N., et al. (2020), Physics of the Dark Universe, Volume 30, article id. 100620."
+ }
+ ]
+}
+
+response = 'From: Tim Hostetler\nAddress: twhostetler0@gmail.com\n\nMissing references:\n2020arXiv200310949S\t2021NewA...8301464S\tShekh, S. H. (2021), New Astronomy, Volume 83, article id. 101464.\n2021NewA...8201452A\t2020PDU....3000664A\tArora, S., et al. (2020), Physics of the Dark Universe, Volume 30, article id. 100664.\n2020arXiv200310949S\t2020PDU....3000620S\tSingh, K. N., et al. (2020), Physics of the Dark Universe, Volume 30, article id. 100620.\n'
\ No newline at end of file
diff --git a/adsws/tests/stubdata/new_abstract.py b/adsws/tests/stubdata/new_abstract.py
new file mode 100644
index 0000000..7587fea
--- /dev/null
+++ b/adsws/tests/stubdata/new_abstract.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+data = {
+ "origin": "user_submission",
+ "g-recaptcha-response": "correct_response",
+ "_subject": "New Record",
+ "original": {
+ "entryType": "new",
+ "name": "",
+ "email": "",
+ "collection": [
+ "astronomy"
+ ],
+ "bibcode": "",
+ "title": "",
+ "authors": [
+
+ ],
+ "publication": "",
+ "publicationDate": "",
+ "urls": [
+ {
+ "value": ""
+ }
+ ],
+ "abstract": "",
+ "keywords": [
+ {
+ "value": ""
+ }
+ ],
+ "references": [
+ {
+ "value": ""
+ }
+ ],
+ "comments": "",
+ "recaptcha": ""
+ },
+ "new": {
+ "collection": [
+ "astronomy"
+ ],
+ "bibcode": "",
+ "title": "test",
+ "authors": [
+ {
+ "id": "Shekh, S. H._0",
+ "position": 0,
+ "name": "Shekh, S. H.",
+ "aff": "Department of Mathematics, S. P. M. Science and Gilani Arts Commerece College Ghatanji, Maharashtra 445301, India",
+ "orcid": ""
+ },
+ {
+ "id": "Shekh, S. H._0",
+ "position": 0,
+ "name": "Foo, Bar",
+ "aff": "Department of Mathematics, S. P. M. Science and Gilani Arts Commerece College Ghatanji, Maharashtra 445301, India",
+ "orcid": ""
+ },
+ ],
+ "publication": "test",
+ "publicationDate": "2020-01",
+ "urls": [
+ {
+ "type": "none",
+ "value": ""
+ }
+ ],
+ "abstract": "Present analysis dedicated to the dynamical investigation of anisotropic dark energy LRS Bianchi type-I cosmological model in the context of modified gravity in which Langrangian be the arbitrary function of Ricci scalar and Gouss-Bonnet invariant say f(R, G) gravity in the way of anisotropic fluid. The classification of the field equations towards f(R , G) =f0RmG 1 - m make available that the model is purely accelerating corresponds to 0 ~ q ~ - 1 . We govern the features of the derived cosmological model in view of the hybrid law inflation in bounce form (which involve power and de-Sitter cosmology) for the average scale factor. Also discussed the singularity of the model with the help of curvature of the model. It is observed that the model is fully engaged with both matter which exist initially for short expansion and dark energy dominated era and rests existing in quintessence dominated era and for sufficiently large time derived model forecasts that the anisotropy of the model will damp out and the Universe will turn out to be isotropic one and also observed that for quintessence dominated era the temperature and entropy density of anticipated model are positive definite along with some physical and kinematical parameters of the bounce model is also discussed in details.",
+ "keywords": [],
+ "references": [],
+ "comments": "test"
+ },
+ "name": "Tim Hostetler",
+ "email": "twhostetler0@gmail.com",
+ "diff": ""
+}
+
+response = 'From: Tim Hostetler\nAddress: twhostetler0@gmail.com\n\nAbstract data for new record. Summary:\n\nCollection: astronomy\n\n%R \n%T test\n%A Shekh, S. H.;Foo, Bar\n%D 2020-01\n%J test\n%B Present analysis dedicated to the dynamical investigation of anisotropic dark energy LRS Bianchi type-I cosmological model in the context of modified gravity in which Langrangian be the arbitrary function of Ricci scalar and Gouss-Bonnet invariant say f(R, G) gravity in the way of anisotropic fluid. The classification of the field equations towards f(R , G) =f0RmG 1 - m make available that the model is purely accelerating corresponds to 0 ~ q ~ - 1 . We govern the features of the derived cosmological model in view of the hybrid law inflation in bounce form (which involve power and de-Sitter cosmology) for the average scale factor. Also discussed the singularity of the model with the help of curvature of the model. It is observed that the model is fully engaged with both matter which exist initially for short expansion and dark energy dominated era and rests existing in quintessence dominated era and for sufficiently large time derived model forecasts that the anisotropy of the model will damp out and the Universe will turn out to be isotropic one and also observed that for quintessence dominated era the temperature and entropy density of anticipated model are positive definite along with some physical and kinematical parameters of the bounce model is also discussed in details.'
\ No newline at end of file
diff --git a/adsws/tests/test_feedback.py b/adsws/tests/test_feedback.py
index 6351b56..8801a01 100644
--- a/adsws/tests/test_feedback.py
+++ b/adsws/tests/test_feedback.py
@@ -11,7 +11,8 @@
from flask import url_for, current_app
from flask.ext.testing import TestCase
from httpretty import HTTPretty
-from adsws.feedback.views import SlackFeedback, verify_recaptcha
+from adsws.feedback.views import UserFeedback, verify_recaptcha
+from adsws.tests.stubdata import missing_references, associated_other, associated_errata, new_abstract, corrected_abstract, general_feedback
class GoogleRecaptchaService(object):
@@ -33,7 +34,6 @@ def request_callback(request, uri, headers):
:return: httpretty response
"""
data = request.parsed_body
-
if data['response'][0] == 'correct_response':
res = {'success': True}
elif data['response'][0] == 'incorrect_response':
@@ -136,7 +136,28 @@ def create_app(self):
"""
Create the wsgi application
"""
- app_ = feedback.create_app()
+ app_ = feedback.create_app(
+ FEEDBACK_SLACK_END_POINT = 'https://hooks.slack.com/services/TOKEN/TOKEN',
+ FEEDBACK_SLACK_EMOJI = ':interrobang:',
+ FORM_SLACK_EMOJI = ':inbox_tray:',
+ DEFAULT_EMAIL = 'adshelp@cfa.harvard.edu',
+ FEEDBACK_FORMS_ORIGIN = 'user_submission',
+ BBB_FEEDBACK_ORIGIN = 'bbb_feedback',
+ FEEDBACK_TEMPLATES = {
+ 'Missing References': 'missing_references.txt',
+ 'Associated Articles': 'associated_articles.txt',
+ 'Updated Record': 'updated_record.txt',
+ 'New Record': 'new_record.txt',
+ 'Bumblebee Feedback':'bumblebee_feedback.txt'
+ },
+ FEEDBACK_EMAILS = {
+ 'Missing References': 'ads@cfa.harvard.edu',
+ },
+ MAIL_SUPPRESS_SEND=True,
+ GOOGLE_RECAPTCHA_ENDPOINT = 'https://www.google.com/recaptcha/api/siteverify',
+ GOOGLE_RECAPTCHA_PRIVATE_KEY = 'MY_PRIVATE_KEY'
+
+ )
return app_
@@ -150,56 +171,162 @@ def test_submitting_feedback(self):
A generic test of the entire work flow of the feedback submission
end point
"""
+ # User presses submit on the feedback form
+ url = url_for('userfeedback')
+ with SlackWebService() as SLW, GoogleRecaptchaService() as GRS:
+ response = self.client.post(
+ url,
+ data=json.dumps(general_feedback.data)
+ )
+ self.assertEqual(response.status_code, 200)
+
+ def test_submitting_missing_references(self):
+ """
+ A generic test of the entire work flow of the feedback submission
+ end point: submissions of missing references
+ """
+
+ # User presses submit on the feedback form
+ url = url_for('userfeedback')
+ with SlackWebService() as SLW, GoogleRecaptchaService() as GRS:
+ response = self.client.post(
+ url,
+ data=json.dumps(missing_references.data)
+ )
+ self.assertEqual(response.status_code, 200)
+
+ def test_submitting_associated(self):
+ """
+ A generic test of the entire work flow of the feedback submission
+ end point: submissions of associated records
+ """
+
+ # User presses submit on the feedback form
+ url = url_for('userfeedback')
+ with SlackWebService() as SLW, GoogleRecaptchaService() as GRS:
+ response = self.client.post(
+ url,
+ data=json.dumps(associated_errata.data)
+ )
+ self.assertEqual(response.status_code, 200)
+
+ def test_submitting_associated_other(self):
+ """
+ A generic test of the entire work flow of the feedback submission
+ end point: submissions of associated records of type 'other'
+ """
+
+ # User presses submit on the feedback form
+ url = url_for('userfeedback')
+ with SlackWebService() as SLW, GoogleRecaptchaService() as GRS:
+ response = self.client.post(
+ url,
+ data=json.dumps(associated_other.data)
+ )
+ self.assertEqual(response.status_code, 200)
+
+ def test_submitting_new_abstract(self):
+ """
+ A generic test of the entire work flow of the feedback submission
+ end point: submissions of new abstract
+ """
+
+ # User presses submit on the feedback form
+ url = url_for('userfeedback')
+ with SlackWebService() as SLW, GoogleRecaptchaService() as GRS:
+ response = self.client.post(
+ url,
+ data=json.dumps(new_abstract.data)
+ )
+ self.assertEqual(response.status_code, 200)
+
+ def test_submitting_corrected_abstract(self):
+ """
+ A generic test of the entire work flow of the feedback submission
+ end point: submissions of new abstract
+ """
+
+ # User presses submit on the feedback form
+ url = url_for('userfeedback')
+ with SlackWebService() as SLW, GoogleRecaptchaService() as GRS:
+ response = self.client.post(
+ url,
+ data=json.dumps(corrected_abstract.data)
+ )
+ self.assertEqual(response.status_code, 200)
+
+ def test_submitting_feedback_with_minimal_information(self):
+ """
+ Check they can send minimal information to the end point
+ """
+ # User presses submit on the feedback form
+ url = url_for('userfeedback')
+ with SlackWebService() as SLW, GoogleRecaptchaService() as GRS:
+ response = self.client.post(
+ url,
+ data=json.dumps(general_feedback.data)
+ )
+ self.assertEqual(response.status_code, 200)
+
+ def test_404_if_not_right_data(self):
+ """
+ Checks the passed data, at the moment we accept specific fields, and so it will not work if the user does not
+ supply any comments
+ """
# User fills the user feedback form
form_data = {
'name': 'Commenter',
- 'comments': 'Why are my citations missing?',
'_replyto': 'commenter@email.com',
- 'g-recaptcha-response': 'correct_response'
+ 'g-recaptcha-response': 'correct_response',
+ 'origin': 'bbb_feedback'
}
# User presses submit on the feedback form
- url = url_for('slackfeedback')
+ url = url_for('userfeedback')
with SlackWebService() as SLW, GoogleRecaptchaService() as GRS:
response = self.client.post(
url,
data=form_data
)
- self.assertEqual(response.status_code, 200)
-
- def test_submitting_feedback_with_minimal_information(self):
+ self.assertEqual(response.status_code, 404)
+
+ def test_404_if_not_right_origin(self):
"""
- Check they can send minimal information to the end point
+ Checks the passed data. The endpoint expects specific values for the 'origin' attribute
"""
# User fills the user feedback form
form_data = {
- 'comments': 'Why are my citations missing?',
- 'g-recaptcha-response': 'correct_response'
+ 'name': 'Commenter',
+ '_replyto': 'commenter@email.com',
+ 'g-recaptcha-response': 'correct_response',
+ 'origin': 'foobar'
}
# User presses submit on the feedback form
- url = url_for('slackfeedback')
+ url = url_for('userfeedback')
with SlackWebService() as SLW, GoogleRecaptchaService() as GRS:
response = self.client.post(
url,
data=form_data
)
- self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.status_code, 404)
- def test_404_if_not_right_data(self):
+ def test_404_if_not_right_subject(self):
"""
- Checks the passed data, at the moment we accept specific fields, and so it will not work if the user does not
- supply any comments
+ Checks the passed data. For user submission feedback, the value _subject field
+ determines the email template. Exception is thrown when this has an unexpected value.
"""
# User fills the user feedback form
form_data = {
'name': 'Commenter',
'_replyto': 'commenter@email.com',
- 'g-recaptcha-response': 'correct_response'
+ 'g-recaptcha-response': 'correct_response',
+ 'origin': 'user_submission',
+ '_subject': 'foo'
}
# User presses submit on the feedback form
- url = url_for('slackfeedback')
+ url = url_for('userfeedback')
with SlackWebService() as SLW, GoogleRecaptchaService() as GRS:
response = self.client.post(
url,
@@ -207,7 +334,6 @@ def test_404_if_not_right_data(self):
)
self.assertEqual(response.status_code, 404)
-
class TestUnits(TestBase):
"""
Class that contains all of the unit tests required for the slack feedback
@@ -248,64 +374,25 @@ def test_mock_of_slack_endpoint_fail(self):
self.assertEqual(response.status_code, 400)
self.assertEqual(response.json()['msg'], 'fail')
- def test_parser_parses_content(self):
+ def test_email_body(self):
"""
- Tests that the input given is parsed sensibly for slack
+
"""
- emoji = current_app.config['FEEDBACK_SLACK_EMOJI']
- post_data_sent = {
- 'text': '```Incoming Feedback```\n'
- '*Commenter*: Commenter\n'
- '*e-mail*: commenter@email.com\n'
- '*Feedback*: Why are my citations missing?\n'
- '*sent to adshelp*: failed\n',
- 'username': 'TownCrier',
- 'channel': '#feedback',
- 'icon_emoji': emoji
- }
-
- form_data = {
- 'name': 'Commenter',
- 'comments': 'Why are my citations missing?',
- '_replyto': 'commenter@email.com'
- }
-
- prettified_post_data = SlackFeedback().prettify_post(form_data)
-
- for key in post_data_sent.keys():
- self.assertEqual(post_data_sent[key], prettified_post_data[key])
-
- def test_can_send_abritrary_keyword_values(self):
- """
- Test the end point is not restrictive on the keyword values it can
- create content for.
- """
- emoji = current_app.config['FEEDBACK_SLACK_EMOJI']
- post_data_sent = {
- 'text': '```Incoming Feedback```\n'
- '*Commenter*: Commenter\n'
- '*e-mail*: commenter@email.com\n'
- '*Feedback*: Why are my citations missing?\n'
- '*IP Address*: 127.0.0.1\n'
- '*Browser*: Firefox v42\n'
- '*sent to adshelp*: failed\n',
- 'username': 'TownCrier',
- 'channel': '#feedback',
- 'icon_emoji': emoji
- }
-
- form_data = {
- 'name': 'Commenter',
- 'comments': 'Why are my citations missing?',
- 'Browser': 'Firefox v42',
- 'IP Address': '127.0.0.1',
- '_replyto': 'commenter@email.com'
- }
-
- prettified_post_data = SlackFeedback().prettify_post(form_data)
- for key in post_data_sent.keys():
- self.assertEqual(post_data_sent[key], prettified_post_data[key])
+ email_body = UserFeedback().create_email_body(corrected_abstract.data)
+ self.assertEqual(email_body, corrected_abstract.response)
+
+ email_body = UserFeedback().create_email_body(new_abstract.data)
+ self.assertEqual(email_body, new_abstract.response)
+
+ email_body = UserFeedback().create_email_body(associated_other.data)
+ self.assertEqual(email_body, associated_other.response)
+
+ email_body = UserFeedback().create_email_body(missing_references.data)
+ self.assertEqual(email_body, missing_references.response)
+
+ email_body = UserFeedback().create_email_body(general_feedback.data)
+ self.assertEqual(email_body, general_feedback.response)
def test_verify_google_recaptcha(self):
"""