From 64d535421563b070efd9067ba4dc190574241fa8 Mon Sep 17 00:00:00 2001 From: Thomas NOEL Date: Tue, 1 May 2018 12:22:19 +0200 Subject: [PATCH] do not add an evolution on static jump (#22236) --- tests/test_form_pages.py | 38 ++++++++++++++++++++++++++++++++++---- tests/test_sql.py | 2 ++ wcs/admin/forms.py | 4 +--- wcs/formdata.py | 24 +++++++++++++++++++----- wcs/sql.py | 36 +++++++++++++++++++++++++++--------- wcs/wf/jump.py | 5 +---- wcs/workflows.py | 4 ++-- 7 files changed, 86 insertions(+), 27 deletions(-) diff --git a/tests/test_form_pages.py b/tests/test_form_pages.py index bf264600..9db7f979 100644 --- a/tests/test_form_pages.py +++ b/tests/test_form_pages.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +import datetime import json import pytest import hashlib @@ -3766,17 +3767,46 @@ def test_form_worklow_multiple_identical_status(pub): user.roles = [role.id] user.store() + assert len(formdef.data_class().get(formdata.id).evolution) == 1 + assert formdef.data_class().get(formdata.id).evolution[0].last_jump_datetime is None + login(app, username='foo', password='foo') resp = app.post(formdata.get_url() + 'jump/trigger/XXX', status=302) + formdata = formdef.data_class().get(formdata.id) + # status is not changed: no new evolution, only a new last_jump_datetime + assert len(formdata.evolution) == 1 + assert formdata.status == 'wf-st1' + assert formdata.evolution[0].last_jump_datetime is not None + + # add a comment to last evolution, forcing create a new one + formdata.evolution[-1].comment = 'new-evolution-1' + formdata.store() resp = app.post(formdata.get_url() + 'jump/trigger/XXX', status=302) + formdata = formdef.data_class().get(formdata.id) + assert len(formdata.evolution) == 2 + assert formdata.status == 'wf-st1' + + # again + formdata.evolution[-1].comment = 'new-evolution-2' + formdata.store() resp = app.post(formdata.get_url() + 'jump/trigger/XXX', status=302) - assert len(formdef.data_class().get(formdata.id).evolution) == 4 - assert formdef.data_class().get(formdata.id).status == 'wf-st1' + # last evolution is empty, this last trigger does not create a new one + resp = app.post(formdata.get_url() + 'jump/trigger/XXX', status=302) + + # finally, 3 evolutions: new-evolution-1, new-evolution-2, empty + formdata = formdef.data_class().get(formdata.id) + assert len(formdata.evolution) == 3 + assert formdata.status == 'wf-st1' + assert formdata.evolution[0].comment == 'new-evolution-1' + assert formdata.evolution[1].comment == 'new-evolution-2' + assert formdata.evolution[2].comment is None resp = app.get(formdata.get_url()) - assert resp.body.count('Status1') == 2 # once in summary and once in journal - assert resp.body.count('CSS-STATUS1') == 1 + assert resp.body.count('Status1') == 3 # once in summary and two in journal + assert resp.body.count('CSS-STATUS1') == 2 + assert resp.body.count('new-evolution-1') == 1 + assert resp.body.count('new-evolution-2') == 1 def test_display_message(pub): user = create_user(pub) diff --git a/tests/test_sql.py b/tests/test_sql.py index 2ec226cb..892b9bad 100644 --- a/tests/test_sql.py +++ b/tests/test_sql.py @@ -1456,6 +1456,7 @@ def test_last_update_time(): formdata1 = data_class() formdata1.status = 'wf-st1' formdata1.just_created() + formdata1.evolution[0].comment = 'comment' formdata1.jump_status('st1') # will add another evolution entry formdata1.evolution[0].time = datetime.datetime(2015, 1, 1, 0, 0, 0).timetuple() formdata1.evolution[1].time = datetime.datetime(2015, 1, 2, 0, 0, 0).timetuple() @@ -1464,6 +1465,7 @@ def test_last_update_time(): formdata2 = data_class() formdata2.status = 'wf-st1' formdata2.just_created() + formdata2.evolution[0].comment = 'comment' formdata2.jump_status('st1') # will add another evolution entry formdata2.evolution[0].time = datetime.datetime(2015, 1, 3, 0, 0, 0).timetuple() formdata2.evolution[1].time = datetime.datetime(2015, 1, 4, 0, 0, 0).timetuple() diff --git a/wcs/admin/forms.py b/wcs/admin/forms.py index cbf9ba80..f459bd88 100644 --- a/wcs/admin/forms.py +++ b/wcs/admin/forms.py @@ -1156,9 +1156,7 @@ class FormDefPage(Directory): if form.get_widget('date').parse(): date = form.get_widget('date').parse() date = time.strptime(date, misc.date_format()) - all_forms = [x for x in all_forms if ( - x.evolution and x.evolution[-1].time < date) or ( - x.receipt_time < date)] + all_forms = [x for x in all_forms if x.last_update_time < date] self.fd = StringIO() t = tarfile.open('wcs.tar.gz', 'w:gz', fileobj=self.fd) diff --git a/wcs/formdata.py b/wcs/formdata.py index 5d84da9c..fb5fee2a 100644 --- a/wcs/formdata.py +++ b/wcs/formdata.py @@ -131,6 +131,7 @@ class Evolution(object): who = None status = None time = None + last_jump_datetime = None comment = None parts = None @@ -179,6 +180,7 @@ class Evolution(object): def get_json_export_dict(self, user, anonymise=False): data = { 'time': self.time, + 'last_jump_datetime': self.last_jump_datetime, } if self.status: data['status'] = self.status[3:] @@ -536,13 +538,23 @@ class FormData(StorableObject): previous_status = self.pop_previous_marked_status() assert previous_status, 'failed to compute previous status' status_id = previous_status.id - evo = Evolution(self) - evo.time = time.localtime() - evo.status = 'wf-%s' % status_id + status = 'wf-%s' % status_id if not self.evolution: self.evolution = [] + elif (self.status == status + and self.evolution[-1].status == status + and not self.evolution[-1].comment + and not self.evolution[-1].display_parts()): + # if status do not change and last evolution is empty, + # just update last jump time on last evolution, do not add one + self.evolution[-1].last_jump_datetime = datetime.datetime.now() + self.store() + return + evo = Evolution(self) + evo.time = time.localtime() + evo.status = status self.evolution.append(evo) - self.status = evo.status + self.status = status self.store() def get_url(self, backoffice = False): @@ -830,7 +842,9 @@ class FormData(StorableObject): def get_last_update_time(self): if hasattr(self, '_last_update_time'): return self._last_update_time - if self.evolution and self.evolution[-1].time: + if self.evolution and self.evolution[-1].last_jump_datetime: + return self.evolution[-1].last_jump_datetime.timetuple() + elif self.evolution and self.evolution[-1].time: return self.evolution[-1].time else: return self.receipt_time diff --git a/wcs/sql.py b/wcs/sql.py index 2a1459df..c6b60ac9 100644 --- a/wcs/sql.py +++ b/wcs/sql.py @@ -371,6 +371,7 @@ def do_formdef_tables(formdef, conn=None, cur=None, rebuild_views=False, rebuild who varchar, status varchar, time timestamp, + last_jump_datetime timestamp, comment text, parts bytea, formdata_id integer REFERENCES %s (id) ON DELETE CASCADE)''' % ( @@ -463,6 +464,15 @@ def do_formdef_tables(formdef, conn=None, cur=None, rebuild_views=False, rebuild for field in (existing_fields - needed_fields): cur.execute('''ALTER TABLE %s DROP COLUMN %s CASCADE''' % (table_name, field)) + # migrations on _evolutions table + cur.execute('''SELECT column_name FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = '%s_evolutions' + ''' % table_name) + evo_existing_fields = set([x[0] for x in cur.fetchall()]) + if 'last_jump_datetime' not in evo_existing_fields: + cur.execute('''ALTER TABLE %s_evolutions ADD COLUMN last_jump_datetime timestamp''' % table_name) + if rebuild_views or len(existing_fields - needed_fields): # views may have been dropped when dropping columns, so we recreate # them even if not asked to. @@ -1192,7 +1202,8 @@ class SqlFormData(SqlMixin, wcs.formdata.FormData): self._evolution = [] return self._evolution conn, cur = get_connection_and_cursor() - sql_statement = '''SELECT id, who, status, time, comment, parts FROM %s_evolutions + sql_statement = '''SELECT id, who, status, time, last_jump_datetime, + comment, parts FROM %s_evolutions WHERE formdata_id = %%(id)s ORDER BY id''' % self._table_name cur.execute(sql_statement, {'id': self.id}) @@ -1209,11 +1220,12 @@ class SqlFormData(SqlMixin, wcs.formdata.FormData): @classmethod def _row2evo(cls, row, formdata): o = wcs.formdata.Evolution(formdata) - o._sql_id, o.who, o.status, o.time, o.comment = [str_encode(x) for x in tuple(row[:5])] + o._sql_id, o.who, o.status, o.time, o.last_jump_datetime, o.comment = [ + str_encode(x) for x in tuple(row[:6])] if o.time: o.time = o.time.timetuple() - if row[5]: - o.parts = cPickle.loads(str(row[5])) + if row[6]: + o.parts = cPickle.loads(str(row[6])) return o def set_evolution(self, value): @@ -1234,7 +1246,8 @@ class SqlFormData(SqlMixin, wcs.formdata.FormData): if not object_dict: return conn, cur = get_connection_and_cursor() - sql_statement = '''SELECT id, who, status, time, comment, parts, formdata_id + sql_statement = '''SELECT id, who, status, time, last_jump_datetime, + comment, parts, formdata_id FROM %s_evolutions''' % cls._table_name sql_statement += ''' WHERE formdata_id IN %(object_ids)s ORDER BY id''' cur.execute(sql_statement, {'object_ids': tuple(object_dict.keys())}) @@ -1246,7 +1259,7 @@ class SqlFormData(SqlMixin, wcs.formdata.FormData): row = cur.fetchone() if row is None: break - _sql_id, who, status, time, comment, parts, formdata_id = tuple(row[:7]) + _sql_id, who, status, time, last_jump_datetime, comment, parts, formdata_id = tuple(row[:8]) formdata = object_dict.get(formdata_id) if not formdata: continue @@ -1340,6 +1353,7 @@ class SqlFormData(SqlMixin, wcs.formdata.FormData): sql_dict.update({'id': evo._sql_id}) sql_statement = '''UPDATE %s_evolutions SET time = %%(time)s, + last_jump_datetime = %%(last_jump_datetime)s, status = %%(status)s, comment = %%(comment)s, parts = %%(parts)s @@ -1348,16 +1362,19 @@ class SqlFormData(SqlMixin, wcs.formdata.FormData): else: sql_statement = '''INSERT INTO %s_evolutions ( id, who, status, - time, comment, parts, + time, last_jump_datetime, + comment, parts, formdata_id) VALUES (DEFAULT, %%(who)s, %%(status)s, - %%(time)s, %%(comment)s, + %%(time)s, %%(last_jump_datetime)s, + %%(comment)s, %%(parts)s, %%(formdata_id)s) RETURNING id''' % self._table_name sql_dict.update({ 'who': evo.who, 'status': evo.status, 'time': datetime.datetime.fromtimestamp(time.mktime(evo.time)), + 'last_jump_datetime': evo.last_jump_datetime, 'comment': evo.comment, 'formdata_id': self.id, }) @@ -2105,7 +2122,7 @@ def get_yearly_totals(period_start=None, period_end=None, criterias=None): return result -SQL_LEVEL = 25 +SQL_LEVEL = 26 def migrate_global_views(conn, cur): cur.execute('''SELECT COUNT(*) FROM information_schema.tables @@ -2179,6 +2196,7 @@ def migrate(): # 19: add geolocation to views # 20: remove user hash stuff # 22: rebuild views + # 26: add last_jump_datetime in evolutions tables migrate_views(conn, cur) if sql_level < 21: # 3: introduction of _structured for user fields diff --git a/wcs/wf/jump.py b/wcs/wf/jump.py index 68bd442a..43cedfec 100644 --- a/wcs/wf/jump.py +++ b/wcs/wf/jump.py @@ -235,10 +235,7 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem): if self.timeout: timeout = int(self.compute(self.timeout)) - if formdata.evolution: - last = formdata.evolution[-1].time - else: - last = formdata.receipt_time + last = formdata.last_update_time if last: diff = time.time() - time.mktime(last) must_jump = (diff > timeout) and must_jump diff --git a/wcs/workflows.py b/wcs/workflows.py index 6e87b374..30753300 100644 --- a/wcs/workflows.py +++ b/wcs/workflows.py @@ -1033,13 +1033,13 @@ class WorkflowGlobalActionTimeoutTrigger(WorkflowGlobalActionTrigger): anchor_status = self.anchor_status_first or formdata.status for evolution in formdata.evolution: if evolution.status == anchor_status: - anchor_date = evolution.time + anchor_date = evolution.last_jump_datetime or evolution.time break elif self.anchor == 'latest-arrival': anchor_status = self.anchor_status_latest or formdata.status for evolution in reversed(formdata.evolution): if evolution.status == anchor_status: - anchor_date = evolution.time + anchor_date = evolution.last_jump_datetime or evolution.time break elif self.anchor == 'python': variables = get_publisher().substitutions.get_context_variables() -- 2.17.0