# -*- coding: utf-8 -*-
#
# This file is part of INSPIRE.
# Copyright (C) 2014-2017 CERN.
#
# INSPIRE is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# INSPIRE is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with INSPIRE. If not, see <http://www.gnu.org/licenses/>.
#
# In applying this license, CERN does not waive the privileges and immunities
# granted to it by virtue of its status as an Intergovernmental Organization
# or submit itself to any jurisdiction.
"""Contains INSPIRE specific submission tasks."""
from __future__ import absolute_import, division, print_function
import os
import logging
from copy import copy
from functools import wraps
from pprint import pformat
import backoff
import rt
from flask import current_app
from invenio_accounts.models import User
from invenio_db import db
from inspire_dojson import record2marcxml
from inspirehep.modules.workflows.models import WorkflowsPendingRecord
from inspirehep.modules.workflows.tasks.actions import in_production_mode
from inspirehep.modules.workflows.utils import with_debug_logging
from inspirehep.utils.robotupload import make_robotupload_marcxml
from inspirehep.utils import tickets
from inspirehep.utils.proxies import rt_instance
LOGGER = logging.getLogger(__name__)
@with_debug_logging
@backoff.on_exception(backoff.expo, rt.ConnectionError, base=4, max_tries=5)
[docs]def submit_rt_ticket(obj,
queue,
template,
context,
requestors,
recid,
ticket_id_key):
"""Submit ticket to RT with the given parameters."""
new_ticket_id = tickets.create_ticket_with_template(queue,
requestors,
template,
context,
context.get("subject"),
recid)
obj.extra_data[ticket_id_key] = new_ticket_id
obj.log.info(u'Ticket {0} created'.format(new_ticket_id))
return new_ticket_id
[docs]def create_ticket(template,
context_factory=None,
queue="Test",
ticket_id_key="ticket_id"):
"""Create a ticket for the submission.
Creates the ticket in the given queue and stores the ticket ID
in the extra_data key specified in ticket_id_key.
"""
@with_debug_logging
@wraps(create_ticket)
def _create_ticket(obj, eng):
user = User.query.get(obj.id_user)
context = {}
if context_factory:
context = context_factory(user, obj)
if not in_production_mode():
obj.log.info(
u'Was going to create ticket: {subject}\n'
u'To: {requestors} Queue: {queue}'.format(
queue=queue,
subject=context.get('subject'),
requestors=user.email if user else '',
)
)
return {}
recid = obj.extra_data.get("recid") or obj.data.get("control_number")
new_ticket_id = submit_rt_ticket(
obj,
queue,
template,
context,
user.email if user else '',
recid,
ticket_id_key
)
return {ticket_id_key: new_ticket_id}
return _create_ticket
[docs]def reply_ticket(template=None,
context_factory=None,
keep_new=False):
"""Reply to a ticket for the submission."""
@with_debug_logging
@wraps(reply_ticket)
def _reply_ticket(obj, eng):
ticket_id_key = "ticket_id"
ticket_id = obj.extra_data.get(ticket_id_key)
if not rt_instance:
obj.log.error("No RT instance available. Skipping!")
obj.log.info(
"Was going to reply to {ticket_id}\n".format(
ticket_id=ticket_id,
)
)
return {}
if not ticket_id:
obj.log.error("No ticket ID found!")
return {}
user = User.query.get(obj.id_user)
if not user:
obj.log.error(
"No user found for object %s, skipping ticket creation", obj.id)
return {}
if template:
context = {}
if context_factory:
context = context_factory(user, obj)
tickets.reply_ticket_with_template(ticket_id,
template,
context,
keep_new)
else:
# Body already rendered in reason.
body = obj.extra_data.get("reason", "")
if body:
tickets.reply_ticket(ticket_id, body, keep_new)
else:
obj.log.error("No body for ticket reply. Skipping reply.")
return {}
return _reply_ticket
[docs]def close_ticket(ticket_id_key="ticket_id"):
"""Close the ticket associated with this record found in given key."""
@with_debug_logging
@wraps(close_ticket)
def _close_ticket(obj, eng):
ticket_id = obj.extra_data.get(ticket_id_key, "")
if not ticket_id:
obj.log.error("No ticket ID found!")
return {}
if not rt_instance:
obj.log.error("No RT instance available. Skipping!")
obj.log.info(
"Was going to close ticket {ticket_id}".format(
ticket_id=ticket_id,
)
)
return {}
tickets.resolve_ticket(ticket_id)
return {}
return _close_ticket
[docs]def send_robotupload(
url=None,
callback_url="callback/workflows/robotupload",
mode="insert",
extra_data_key=None
):
"""Get the MARCXML from the model and ship it.
If callback_url is set the workflow will halt and the callback is
responsible for resuming it.
"""
@with_debug_logging
@wraps(send_robotupload)
def _send_robotupload(obj, eng):
combined_callback_url = ''
if callback_url:
combined_callback_url = os.path.join(
current_app.config["SERVER_NAME"],
callback_url
)
if not combined_callback_url.startswith('http'):
combined_callback_url = "https://{0}".format(
combined_callback_url
)
if extra_data_key is not None:
data = obj.extra_data.get(extra_data_key) or {}
else:
data = obj.data
if not current_app.config.get('FEATURE_FLAG_ENABLE_SENDING_REFERENCES_TO_LEGACY'):
data = copy(data)
data.pop('references', None)
marcxml = record2marcxml(data)
if current_app.debug:
# Log what we are sending
LOGGER.debug(
"Going to robotupload mode:%s to url:%s:\n%s\n",
mode,
url,
marcxml,
)
if not in_production_mode():
obj.log.debug(
"Going to robotupload %s to %s:\n%s\n",
mode,
url,
marcxml,
)
obj.log.debug(
"Base object data:\n%s",
pformat(data)
)
return
result = make_robotupload_marcxml(
url=url,
marcxml=marcxml,
callback_url=combined_callback_url,
mode=mode,
nonce=obj.id,
priority=5,
)
if "[INFO]" not in result.text:
if "cannot use the service" in result.text:
# IP not in the list
obj.log.error("Your IP is not in "
"app.config_BATCHUPLOADER_WEB_ROBOT_RIGHTS "
"on host: %s", result.text)
txt = "Error while submitting robotupload: {0}".format(result.text)
raise Exception(txt)
else:
obj.log.info("Robotupload sent!")
obj.log.info(result.text)
if callback_url:
eng.halt("Waiting for robotupload: {0}".format(result.text))
obj.log.info("end of upload")
return _send_robotupload
[docs]def send_to_legacy(obj, eng):
update_legacy_flag = current_app.config.get('FEATURE_FLAG_ENABLE_UPDATE_TO_LEGACY', False)
if obj.extra_data.get('is-update', False) and not update_legacy_flag:
obj.log.info('skipping upload to legacy, feature flag ``FEATURE_FLAG_ENABLE_UPDATE_TO_LEGACY`` is disabled.')
return
else:
send_robotupload(mode='replace')(obj, eng)
@with_debug_logging
[docs]def wait_webcoll(obj, eng):
if not in_production_mode():
obj.log.debug("Would have wait for webcoll callback.")
return
eng.halt("Waiting for webcoll.")
@with_debug_logging
[docs]def filter_keywords(obj, eng):
"""Removes non-accepted keywords from the metadata"""
prediction = obj.extra_data.get('keywords_prediction', {})
if prediction:
keywords = prediction.get('keywords')
keywords = filter(lambda x: x['accept'], keywords)
obj.extra_data['keywords_prediction']['keywords'] = keywords
obj.log.debug('Filtered keywords: \n%s', pformat(keywords))
obj.log.debug('Got no prediction for keywords')
@with_debug_logging
[docs]def prepare_keywords(obj, eng):
"""Prepares the keywords in the correct format to be sent"""
prediction = obj.extra_data.get('keywords_prediction', {})
if not prediction:
return
keywords = obj.data.get('keywords', [])
for keyword in prediction.get('keywords', []):
# TODO: differentiate between curated and guessed keywords
keywords.append(
{
'value': keyword['label'],
'source': 'curator' if keyword.get('curated') else 'magpie',
}
)
obj.data['keywords'] = keywords
obj.log.debug('Finally got keywords: \n%s', pformat(keywords))
@with_debug_logging
[docs]def cleanup_pending_workflow(obj, eng):
"""Cleans up the pending workflow entry for this workflow if any."""
WorkflowsPendingRecord.query.filter(
WorkflowsPendingRecord.workflow_id == obj.id
).delete()
db.session.commit()