Add basic views to expose Problem Reports, and run them
not very sophisticated yet but heck better than we had yesterday
This commit is contained in:
parent
f687078bbf
commit
871dd35a3a
76
tailbone/templates/reports/problems/view.mako
Normal file
76
tailbone/templates/reports/problems/view.mako
Normal file
|
@ -0,0 +1,76 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<%inherit file="/master/view.mako" />
|
||||
|
||||
<%def name="object_helpers()">
|
||||
${parent.object_helpers()}
|
||||
% if master.has_perm('execute'):
|
||||
<nav class="panel">
|
||||
<p class="panel-heading">Tools</p>
|
||||
<div class="panel-block buttons">
|
||||
<b-button type="is-primary"
|
||||
@click="runReportShowDialog = true"
|
||||
icon-pack="fas"
|
||||
icon-left="arrow-circle-right">
|
||||
Run this Report
|
||||
</b-button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<b-modal has-modal-card
|
||||
:active.sync="runReportShowDialog">
|
||||
<div class="modal-card">
|
||||
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Run Problem Report</p>
|
||||
</header>
|
||||
|
||||
<section class="modal-card-body">
|
||||
<p class="block">
|
||||
You can run this problem report right now if you like.
|
||||
</p>
|
||||
|
||||
<p class="block">
|
||||
Keep in mind the following may receive email, should the
|
||||
report find any problems.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
% for recip in instance['email_recipients']:
|
||||
<li>${recip}</li>
|
||||
% endfor
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<footer class="modal-card-foot">
|
||||
<b-button @click="runReportShowDialog = false">
|
||||
Cancel
|
||||
</b-button>
|
||||
${h.form(master.get_action_url('execute', instance))}
|
||||
${h.csrf_token(request)}
|
||||
<b-button type="is-primary"
|
||||
native-type="submit"
|
||||
@click="runReportSubmitting = true"
|
||||
:disabled="runReportSubmitting"
|
||||
icon-pack="fas"
|
||||
icon-left="arrow-circle-right">
|
||||
{{ runReportSubmitting ? "Working, please wait..." : "Run Problem Report" }}
|
||||
</b-button>
|
||||
${h.end_form()}
|
||||
</footer>
|
||||
</div>
|
||||
</b-modal>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="modify_this_page_vars()">
|
||||
${parent.modify_this_page_vars()}
|
||||
<script type="text/javascript">
|
||||
|
||||
ThisPageData.runReportShowDialog = false
|
||||
ThisPageData.runReportSubmitting = false
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
|
||||
${parent.body()}
|
|
@ -850,7 +850,7 @@ class BatchMasterView(MasterView):
|
|||
|
||||
# launch thread to invoke handler action
|
||||
thread = Thread(target=self.action_subprocess_thread,
|
||||
args=(batch.uuid, port, username, batch_action, progress),
|
||||
args=((batch.uuid,), port, username, batch_action, progress),
|
||||
kwargs=kwargs)
|
||||
thread.start()
|
||||
|
||||
|
@ -859,7 +859,7 @@ class BatchMasterView(MasterView):
|
|||
|
||||
# launch thread to populate batch; that will update session progress directly
|
||||
target = getattr(self, '{}_thread'.format(batch_action))
|
||||
thread = Thread(target=target, args=(batch.uuid, user_uuid, progress), kwargs=kwargs)
|
||||
thread = Thread(target=target, args=((batch.uuid,), user_uuid, progress), kwargs=kwargs)
|
||||
thread.start()
|
||||
|
||||
return self.render_progress(progress, {
|
||||
|
@ -894,7 +894,7 @@ class BatchMasterView(MasterView):
|
|||
log.debug("launching command in subprocess: %s", cmd)
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
def action_subprocess_thread(self, batch_uuid, port, username, handler_action, progress, **kwargs):
|
||||
def action_subprocess_thread(self, key, port, username, handler_action, progress, **kwargs):
|
||||
"""
|
||||
This method is sort of an alternative thread target for batch actions,
|
||||
to be used in the event versioning is enabled for the main process but
|
||||
|
@ -902,6 +902,8 @@ class BatchMasterView(MasterView):
|
|||
launch a separate process with versioning disabled in order to act on
|
||||
the batch.
|
||||
"""
|
||||
batch_uuid = key[0]
|
||||
|
||||
# figure out the (sub)command args we'll be passing
|
||||
subargs = [
|
||||
'--batch-type',
|
||||
|
@ -1216,7 +1218,7 @@ class BatchMasterView(MasterView):
|
|||
def execute_error_message(self, error):
|
||||
return "Batch execution failed: {}".format(simple_error(error))
|
||||
|
||||
def execute_thread(self, batch_uuid, user_uuid, progress, **kwargs):
|
||||
def execute_thread(self, key, user_uuid, progress, **kwargs):
|
||||
"""
|
||||
Thread target for executing a batch with progress indicator.
|
||||
"""
|
||||
|
@ -1224,7 +1226,7 @@ class BatchMasterView(MasterView):
|
|||
# session here; can't use tailbone because it has web request
|
||||
# transaction binding etc.
|
||||
session = RattailSession()
|
||||
batch = session.query(self.model_class).get(batch_uuid)
|
||||
batch = self.get_instance_for_key(key, session)
|
||||
user = session.query(model.User).get(user_uuid)
|
||||
try:
|
||||
result = self.handler.do_execute(batch, user=user, progress=progress, **kwargs)
|
||||
|
|
|
@ -1688,36 +1688,43 @@ class MasterView(View):
|
|||
"""
|
||||
obj = self.get_instance()
|
||||
model_title = self.get_model_title()
|
||||
if self.request.method == 'POST':
|
||||
progress = self.make_execute_progress(obj)
|
||||
|
||||
progress = self.make_execute_progress(obj)
|
||||
kwargs = {'progress': progress}
|
||||
thread = Thread(target=self.execute_thread, args=(obj.uuid, self.request.user.uuid), kwargs=kwargs)
|
||||
thread.start()
|
||||
kwargs = {'progress': progress}
|
||||
key = [self.request.matchdict[k]
|
||||
for k in self.get_model_key(as_tuple=True)]
|
||||
thread = Thread(target=self.execute_thread, args=(key, self.request.user.uuid), kwargs=kwargs)
|
||||
thread.start()
|
||||
|
||||
return self.render_progress(progress, {
|
||||
'instance': obj,
|
||||
'initial_msg': self.execute_progress_initial_msg,
|
||||
'cancel_url': self.get_action_url('view', obj),
|
||||
'cancel_msg': "{} execution was canceled".format(model_title),
|
||||
}, template=self.execute_progress_template)
|
||||
|
||||
self.request.session.flash("Sorry, you must POST to execute a {}.".format(model_title), 'error')
|
||||
return self.redirect(self.get_action_url('view', obj))
|
||||
return self.render_progress(progress, {
|
||||
'instance': obj,
|
||||
'initial_msg': self.execute_progress_initial_msg,
|
||||
'cancel_url': self.get_action_url('view', obj),
|
||||
'cancel_msg': "{} execution was canceled".format(model_title),
|
||||
}, template=self.execute_progress_template)
|
||||
|
||||
def make_execute_progress(self, obj):
|
||||
key = '{}.execute'.format(self.get_grid_key())
|
||||
return self.make_progress(key)
|
||||
|
||||
def execute_thread(self, uuid, user_uuid, progress=None, **kwargs):
|
||||
def get_instance_for_key(self, key, session):
|
||||
model_key = self.get_model_key(as_tuple=True)
|
||||
if len(model_key) == 1 and model_key[0] == 'uuid':
|
||||
uuid = key[0]
|
||||
return session.query(self.model_class).get(uuid)
|
||||
raise NotImplementedError
|
||||
|
||||
def execute_thread(self, key, user_uuid, progress=None, **kwargs):
|
||||
"""
|
||||
Thread target for executing an object.
|
||||
"""
|
||||
session = RattailSession()
|
||||
obj = session.query(self.model_class).get(uuid)
|
||||
obj = self.get_instance_for_key(key, session)
|
||||
user = session.query(model.User).get(user_uuid)
|
||||
try:
|
||||
self.execute_instance(obj, user, progress=progress, **kwargs)
|
||||
success_msg = self.execute_instance(obj, user,
|
||||
progress=progress,
|
||||
**kwargs)
|
||||
|
||||
# If anything goes wrong, rollback and log the error etc.
|
||||
except Exception as error:
|
||||
|
@ -1733,13 +1740,21 @@ class MasterView(View):
|
|||
# If no error, check result flag (false means user canceled).
|
||||
else:
|
||||
session.commit()
|
||||
session.refresh(obj)
|
||||
try:
|
||||
needs_refresh = obj in session
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
if needs_refresh:
|
||||
session.refresh(obj)
|
||||
success_url = self.get_execute_success_url(obj)
|
||||
session.close()
|
||||
if progress:
|
||||
progress.session.load()
|
||||
progress.session['complete'] = True
|
||||
progress.session['success_url'] = success_url
|
||||
if success_msg:
|
||||
progress.session['success_msg'] = success_msg
|
||||
progress.session.save()
|
||||
|
||||
def execute_error_message(self, error):
|
||||
|
@ -1991,8 +2006,10 @@ class MasterView(View):
|
|||
the master view class. This is the plural, lower-cased name of the
|
||||
model class by default, e.g. 'products'.
|
||||
"""
|
||||
if hasattr(cls, 'route_prefix'):
|
||||
return cls.route_prefix
|
||||
model_name = cls.get_normalized_model_name()
|
||||
return getattr(cls, 'route_prefix', '{0}s'.format(model_name))
|
||||
return '{}s'.format(model_name)
|
||||
|
||||
@classmethod
|
||||
def get_url_prefix(cls):
|
||||
|
@ -2377,7 +2394,10 @@ class MasterView(View):
|
|||
mapper = orm.object_mapper(row)
|
||||
except orm.exc.UnmappedInstanceError:
|
||||
try:
|
||||
return {self.model_key: row[self.model_key]}
|
||||
if isinstance(self.model_key, six.string_types):
|
||||
return {self.model_key: row[self.model_key]}
|
||||
return dict([(key, row[key])
|
||||
for key in self.model_key])
|
||||
except TypeError:
|
||||
return {self.model_key: getattr(row, self.model_key)}
|
||||
else:
|
||||
|
@ -4311,7 +4331,9 @@ class MasterView(View):
|
|||
if cls.executable:
|
||||
config.add_tailbone_permission(permission_prefix, '{}.execute'.format(permission_prefix),
|
||||
"Execute {}".format(model_title))
|
||||
config.add_route('{}.execute'.format(route_prefix), '{}/execute'.format(instance_url_prefix))
|
||||
config.add_route('{}.execute'.format(route_prefix),
|
||||
'{}/execute'.format(instance_url_prefix),
|
||||
request_method='POST')
|
||||
config.add_view(cls, attr='execute', route_name='{}.execute'.format(route_prefix),
|
||||
permission='{}.execute'.format(permission_prefix))
|
||||
|
||||
|
|
|
@ -43,12 +43,12 @@ import colander
|
|||
from deform import widget as dfwidget
|
||||
from mako.template import Template
|
||||
from pyramid.response import Response
|
||||
from webhelpers2.html import HTML
|
||||
from webhelpers2.html import HTML, tags
|
||||
|
||||
from tailbone import forms
|
||||
from tailbone.db import Session
|
||||
from tailbone.views import View
|
||||
from tailbone.views.exports import ExportMasterView
|
||||
from tailbone.views.exports import ExportMasterView, MasterView
|
||||
|
||||
|
||||
plu_upc_pattern = re.compile(r'^000000000(\d{5})$')
|
||||
|
@ -511,6 +511,140 @@ class NewReport(colander.Schema):
|
|||
validator=valid_report_type)
|
||||
|
||||
|
||||
class ProblemReportView(MasterView):
|
||||
"""
|
||||
Master view for problem reports
|
||||
"""
|
||||
model_title = "Problem Report"
|
||||
model_key = ('system_key', 'problem_key')
|
||||
route_prefix = 'problem_reports'
|
||||
url_prefix = '/reports/problems'
|
||||
|
||||
creatable = False
|
||||
editable = False
|
||||
deletable = False
|
||||
filterable = False
|
||||
pageable = False
|
||||
executable = True
|
||||
|
||||
labels = {
|
||||
'system_key': "System",
|
||||
}
|
||||
|
||||
grid_columns = [
|
||||
'system_key',
|
||||
# 'problem_key',
|
||||
'problem_title',
|
||||
'email_recipients',
|
||||
]
|
||||
|
||||
form_fields = [
|
||||
'system_key',
|
||||
'problem_title',
|
||||
'email_key',
|
||||
'email_recipients',
|
||||
]
|
||||
|
||||
def __init__(self, request):
|
||||
super(ProblemReportView, self).__init__(request)
|
||||
|
||||
app = self.get_rattail_app()
|
||||
self.handler = app.get_problem_report_handler()
|
||||
|
||||
def normalize(self, report, keep_report=True):
|
||||
data = {
|
||||
'system_key': report.system_key,
|
||||
'problem_key': report.problem_key,
|
||||
'problem_title': report.problem_title,
|
||||
'email_key': self.handler.get_email_key(report),
|
||||
}
|
||||
|
||||
app = self.get_rattail_app()
|
||||
handler = app.get_mail_handler()
|
||||
email = handler.get_email(data['email_key'])
|
||||
data['email_recipients'] = email.get_recips('all')
|
||||
|
||||
if keep_report:
|
||||
data['_report'] = report
|
||||
return data
|
||||
|
||||
def get_data(self, session=None):
|
||||
data = []
|
||||
|
||||
reports = self.handler.get_all_problem_reports()
|
||||
organized = self.handler.organize_problem_reports(reports)
|
||||
|
||||
for system_key, reports in six.iteritems(organized):
|
||||
for report in six.itervalues(reports):
|
||||
data.append(self.normalize(report))
|
||||
|
||||
return data
|
||||
|
||||
def configure_grid(self, g):
|
||||
super(ProblemReportView, self).configure_grid(g)
|
||||
|
||||
g.set_renderer('email_recipients', self.render_email_recipients)
|
||||
|
||||
g.set_link('problem_key')
|
||||
g.set_link('problem_title')
|
||||
|
||||
def get_instance(self):
|
||||
system_key = self.request.matchdict['system_key']
|
||||
problem_key = self.request.matchdict['problem_key']
|
||||
return self.get_instance_for_key((system_key, problem_key),
|
||||
None)
|
||||
|
||||
def get_instance_for_key(self, key, session):
|
||||
report = self.handler.get_problem_report(*key)
|
||||
if report:
|
||||
return self.normalize(report)
|
||||
raise self.notfound()
|
||||
|
||||
def get_instance_title(self, report_info):
|
||||
return report_info['problem_title']
|
||||
|
||||
def make_form_schema(self):
|
||||
return ProblemReportSchema()
|
||||
|
||||
def configure_form(self, f):
|
||||
super(ProblemReportView, self).configure_form(f)
|
||||
|
||||
f.set_renderer('email_key', self.render_email_key)
|
||||
f.set_renderer('email_recipients', self.render_email_recipients)
|
||||
|
||||
def render_email_key(self, report_info, field):
|
||||
email_key = report_info[field]
|
||||
if not email_key:
|
||||
return
|
||||
|
||||
if self.request.has_perm('emailprofiles.view'):
|
||||
text = email_key
|
||||
url = self.request.route_url('emailprofiles.view', key=email_key)
|
||||
return tags.link_to(text, url)
|
||||
|
||||
return email_key
|
||||
|
||||
def render_email_recipients(self, report_info, field):
|
||||
recips = report_info['email_recipients']
|
||||
return ', '.join(recips)
|
||||
|
||||
def execute_instance(self, report_info, user, progress=None, **kwargs):
|
||||
report = report_info['_report']
|
||||
problems = self.handler.run_problem_report(report)
|
||||
return "Report found {} problems".format(len(problems))
|
||||
|
||||
|
||||
class ProblemReportSchema(colander.MappingSchema):
|
||||
|
||||
system_key = colander.SchemaNode(colander.String())
|
||||
|
||||
problem_key = colander.SchemaNode(colander.String())
|
||||
|
||||
problem_title = colander.SchemaNode(colander.String())
|
||||
|
||||
email_key = colander.SchemaNode(colander.String())
|
||||
|
||||
|
||||
def add_routes(config):
|
||||
config.add_route('reports.ordering', '/reports/ordering')
|
||||
config.add_route('reports.inventory', '/reports/inventory')
|
||||
|
@ -531,3 +665,4 @@ def includeme(config):
|
|||
# note that GenerateReport must come first, per route matching
|
||||
GenerateReport.defaults(config)
|
||||
ReportOutputView.defaults(config)
|
||||
ProblemReportView.defaults(config)
|
||||
|
|
Loading…
Reference in a new issue