workflows: add support for timeout jumps to the jump action (#3032)
This commit is contained in:
parent
6701eae8c7
commit
095a134575
|
@ -273,6 +273,16 @@ class FormData(StorableObject):
|
|||
return None
|
||||
return wf_status.handle_form(form, self, user)
|
||||
|
||||
def jump_status(self, status_id):
|
||||
evo = Evolution()
|
||||
evo.time = time.localtime()
|
||||
evo.status = 'wf-%s' % status_id
|
||||
if not self.evolution:
|
||||
self.evolution = []
|
||||
self.evolution.append(evo)
|
||||
self.status = evo.status
|
||||
self.store()
|
||||
|
||||
def get_url(self, backoffice = False):
|
||||
return '%s%s/' % (self.formdef.get_url(backoffice=backoffice), self.id)
|
||||
|
||||
|
|
132
wcs/wf/jump.py
132
wcs/wf/jump.py
|
@ -20,10 +20,14 @@ import sys
|
|||
from quixote import get_publisher, get_request, redirect
|
||||
|
||||
from quixote.directory import Directory
|
||||
from qommon.humantime import *
|
||||
from qommon.form import *
|
||||
from qommon import errors
|
||||
from qommon.publisher import get_publisher_class
|
||||
from qommon.cron import CronJob
|
||||
|
||||
from wcs.workflows import WorkflowStatusJumpItem, register_item_class
|
||||
from wcs.workflows import Workflow, WorkflowStatusJumpItem, register_item_class
|
||||
from wcs.formdata import Evolution
|
||||
from wcs.api import get_user_from_api_query_string
|
||||
|
||||
|
||||
|
@ -66,6 +70,7 @@ class TriggerDirectory(Directory):
|
|||
if hasattr(get_request(), 'json'):
|
||||
self.formdata.update_workflow_data(get_request().json)
|
||||
self.formdata.store()
|
||||
formdata.jump_status(self.wfstatusitem.status)
|
||||
url = self.formdata.perform_workflow()
|
||||
if get_request().is_json():
|
||||
return json.dumps({'err': 0, 'url': url})
|
||||
|
@ -85,32 +90,35 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
|
|||
by = []
|
||||
condition = None
|
||||
trigger = None
|
||||
timeout = None
|
||||
_granularity = 20 * 60
|
||||
# default: 20 minutes, see definition of cronjob at the end of the file
|
||||
|
||||
directory_name = 'jump'
|
||||
directory_class = JumpDirectory
|
||||
|
||||
def render_as_line(self):
|
||||
if self.status:
|
||||
wf_status = self.get_status()
|
||||
if not wf_status:
|
||||
return _('Change Status Automatically (broken)')
|
||||
if self.condition:
|
||||
return _('Change Status Automatically (to %(name)s, if %(condition)s)') % {
|
||||
'name': wf_status.name,
|
||||
'condition': self.condition
|
||||
}
|
||||
elif self.trigger:
|
||||
return _('Change Status Automatically (to %(name)s, on %(trigger)s)') % {
|
||||
'name': wf_status.name,
|
||||
'trigger': self.trigger
|
||||
}
|
||||
else:
|
||||
return _('Change Status Automatically (to %s)') % wf_status.name
|
||||
else:
|
||||
if not self.status:
|
||||
return _('Change Status Automatically (not completed)')
|
||||
wf_status = self.get_status()
|
||||
if not wf_status:
|
||||
return _('Change Status Automatically (broken)')
|
||||
reasons = []
|
||||
if self.condition:
|
||||
reasons.append(_('condition'))
|
||||
if self.trigger:
|
||||
reasons.append(_('trigger'))
|
||||
if self.timeout:
|
||||
reasons.append(_('timeout'))
|
||||
|
||||
if reasons:
|
||||
return _('Change Status Automatically (to %s) (%s)') % (
|
||||
wf_status.name, ', '.join(reasons))
|
||||
else:
|
||||
return _('Change Status Automatically (to %s)')
|
||||
|
||||
def get_parameters(self):
|
||||
return ('status', 'condition', 'trigger', 'by')
|
||||
return ('status', 'condition', 'trigger', 'by', 'timeout')
|
||||
|
||||
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
|
||||
WorkflowStatusJumpItem.add_parameters_widgets(self, form, parameters, prefix, formdef)
|
||||
|
@ -128,11 +136,30 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
|
|||
element_kwargs={'render_br': False,
|
||||
'options': [(None, '---')] +
|
||||
self.get_list_of_roles(include_logged_in_users=False)})
|
||||
if 'timeout' in parameters:
|
||||
_hint = htmltext(_("ex.: 7 days 1 minute<br/> Usable units of time: %(variables)s. " \
|
||||
'<br/><span class="warning">Minimal duration is %(granularity)s</span>')) % {
|
||||
'variables': ', '.join(timewords()),
|
||||
'granularity': seconds2humanduration(self._granularity)}
|
||||
form.add(StringWidget, '%stimeout' % prefix, title=_('Timeout'),
|
||||
value=seconds2humanduration(self.timeout), hint=_hint)
|
||||
|
||||
def timeout_parse(self, value):
|
||||
try:
|
||||
return humanduration2seconds(value)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def perform(self, formdata):
|
||||
if not self.status:
|
||||
return
|
||||
|
||||
if self.must_jump(formdata):
|
||||
wf_status = self.get_status()
|
||||
if wf_status:
|
||||
formdata.status = 'wf-%s' % wf_status.id
|
||||
|
||||
def must_jump(self, formdata):
|
||||
must_jump = True
|
||||
|
||||
if self.condition:
|
||||
|
@ -148,9 +175,68 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
|
|||
get_request().trigger_name == self.trigger)
|
||||
must_jump = must_jump and triggered
|
||||
|
||||
if must_jump:
|
||||
wf_status = self.get_status()
|
||||
if wf_status:
|
||||
formdata.status = 'wf-%s' % wf_status.id
|
||||
if self.timeout:
|
||||
if formdata.evolution:
|
||||
last = formdata.evolution[-1].time
|
||||
else:
|
||||
last = formdata.receipt_time
|
||||
if last:
|
||||
diff = time.time() - time.mktime(last)
|
||||
must_jump = (diff > self.timeout) and must_jump
|
||||
|
||||
return must_jump
|
||||
|
||||
register_item_class(JumpWorkflowStatusItem)
|
||||
|
||||
|
||||
def workflows_with_timeout():
|
||||
'''Return a list of workflow objects with at least a status with a timeout object
|
||||
'''
|
||||
wfs_status = {}
|
||||
|
||||
for id in Workflow.keys():
|
||||
workflow = Workflow.get(id, ignore_errors = True)
|
||||
if not workflow:
|
||||
continue
|
||||
for status in workflow.possible_status:
|
||||
status_str = 'wf-%s' % status.id
|
||||
for item in status.items:
|
||||
if hasattr(item, 'status') and hasattr(item, 'timeout') and (
|
||||
item.status and item.timeout):
|
||||
if not wfs_status.has_key(id):
|
||||
wfs_status[id] = {}
|
||||
if not wfs_status[id].has_key(status_str):
|
||||
wfs_status[id][status_str] = []
|
||||
if not item.get_status():
|
||||
# this will catch status being a removed status
|
||||
continue
|
||||
wfs_status[id][status_str].append(item)
|
||||
|
||||
return wfs_status
|
||||
|
||||
def _apply_timeouts(publisher):
|
||||
'''Traverse all filled form and apply expired timeout jumps if needed'''
|
||||
from formdef import FormDef
|
||||
wfs_status = workflows_with_timeout()
|
||||
|
||||
for formdef_id in FormDef.keys():
|
||||
formdef = FormDef.get(formdef_id, ignore_errors = True)
|
||||
if not formdef:
|
||||
continue
|
||||
if str(formdef.workflow_id) in wfs_status.keys():
|
||||
for formdata_id in formdef.data_class().keys():
|
||||
formdata = formdef.data_class().get(formdata_id, ignore_errors = True)
|
||||
if not formdata:
|
||||
continue
|
||||
if formdata.status in wfs_status[str(formdef.workflow_id)]:
|
||||
for x in wfs_status[str(formdef.workflow_id)][formdata.status]:
|
||||
if x.must_jump(formdata):
|
||||
formdata.jump_status(x.status)
|
||||
formdata.perform_workflow()
|
||||
break
|
||||
|
||||
if get_publisher_class():
|
||||
# every 20 minutes check for expired status jump timeouts. If you change
|
||||
# cronjob schedule, change also the granularity variable
|
||||
get_publisher_class().register_cronjob(
|
||||
CronJob(_apply_timeouts, hours=range(24), minutes=range(0, 60, 20)))
|
||||
|
|
|
@ -21,7 +21,6 @@ from wcs.workflows import Workflow, WorkflowStatusJumpItem, register_item_class
|
|||
from wcs.formdata import Evolution
|
||||
from qommon.form import *
|
||||
from qommon.publisher import get_publisher_class
|
||||
from qommon.cron import CronJob
|
||||
|
||||
class TimeoutWorkflowStatusItem(WorkflowStatusJumpItem):
|
||||
description = N_('Change Status on Timeout')
|
||||
|
@ -32,6 +31,10 @@ class TimeoutWorkflowStatusItem(WorkflowStatusJumpItem):
|
|||
|
||||
_granularity = 20 * 60 # default: 20 minutes, see bottom of file
|
||||
|
||||
def is_available(cls):
|
||||
return False
|
||||
is_available = classmethod(is_available)
|
||||
|
||||
def render_as_line(self):
|
||||
if self.status and self.get_status() and self.timeout:
|
||||
return _('Change Status on Timeout to "%(status)s" after %(time)s') % \
|
||||
|
@ -58,71 +61,15 @@ class TimeoutWorkflowStatusItem(WorkflowStatusJumpItem):
|
|||
def get_parameters(self):
|
||||
return ('status', 'timeout')
|
||||
|
||||
def must_jump(self, formdata):
|
||||
if formdata.evolution:
|
||||
last = formdata.evolution[-1].time
|
||||
else:
|
||||
last = formdata.receipt_time
|
||||
if last is None: # shouldn't happen
|
||||
return False
|
||||
diff = time.time() - time.mktime(last)
|
||||
return diff > self.timeout
|
||||
|
||||
def workflows_with_timeout():
|
||||
'''Return a list of workflow objects with at least a status with a timeout object
|
||||
'''
|
||||
wfs_status = {}
|
||||
|
||||
for id in Workflow.keys():
|
||||
workflow = Workflow.get(id, ignore_errors = True)
|
||||
if not workflow:
|
||||
continue
|
||||
for status in workflow.possible_status:
|
||||
status_str = 'wf-%s' % status.id
|
||||
for item in status.items:
|
||||
if isinstance(item, TimeoutWorkflowStatusItem) and item.status and item.timeout:
|
||||
if not wfs_status.has_key(id):
|
||||
wfs_status[id] = {}
|
||||
if not wfs_status[id].has_key(status_str):
|
||||
wfs_status[id][status_str] = []
|
||||
if not item.get_status():
|
||||
# this will catch status being a removed status
|
||||
continue
|
||||
wfs_status[id][status_str].append(item)
|
||||
|
||||
return wfs_status
|
||||
|
||||
def _apply_timeouts(publisher):
|
||||
'''Traverse all filled form and apply expired timeout jumps if needed'''
|
||||
from formdef import FormDef
|
||||
wfs_status = workflows_with_timeout()
|
||||
|
||||
for formdef_id in FormDef.keys():
|
||||
formdef = FormDef.get(formdef_id, ignore_errors = True)
|
||||
if not formdef:
|
||||
continue
|
||||
if str(formdef.workflow_id) in wfs_status.keys():
|
||||
for formdata_id in formdef.data_class().keys():
|
||||
formdata = formdef.data_class().get(formdata_id, ignore_errors = True)
|
||||
if not formdata:
|
||||
continue
|
||||
if formdata.status in wfs_status[str(formdef.workflow_id)]:
|
||||
if formdata.evolution:
|
||||
last = formdata.evolution[-1].time
|
||||
else:
|
||||
last = formdata.receipt_time
|
||||
if last is None: # shouldn't happen
|
||||
continue
|
||||
diff = time.time() - time.mktime(last)
|
||||
for x in wfs_status[str(formdef.workflow_id)][formdata.status]:
|
||||
if diff > x.timeout:
|
||||
evo = Evolution()
|
||||
evo.time = time.localtime()
|
||||
evo.status = 'wf-%s' % x.status
|
||||
if not formdata.evolution:
|
||||
formdata.evolution = []
|
||||
formdata.evolution.append(evo)
|
||||
formdata.status = evo.status
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
break
|
||||
|
||||
register_item_class(TimeoutWorkflowStatusItem)
|
||||
|
||||
if get_publisher_class():
|
||||
# every 20 minutes check for expired status jump timeouts. If you change
|
||||
# cronjob schedule, change also the granularity variable
|
||||
TimeoutWorkflowStatusItem._granularity = 20*60 # 20 minutes
|
||||
get_publisher_class().register_cronjob(
|
||||
CronJob(_apply_timeouts, hours=range(24), minutes=range(0,60,20)))
|
||||
|
|
Loading…
Reference in New Issue