workflows: add support for timeout jumps to the jump action (#3032)

This commit is contained in:
Frédéric Péters 2013-06-07 16:28:35 +02:00
parent 6701eae8c7
commit 095a134575
3 changed files with 132 additions and 89 deletions

View File

@ -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)

View File

@ -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)))

View File

@ -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)))