From 63464d14b4449502f1b8df522bd699746cec961c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 Aug 2020 13:22:18 +0200 Subject: [PATCH 1/5] splitted next_task_update into more parts --- .../ftrack/events/event_next_task_update.py | 249 +++++++++++++----- 1 file changed, 183 insertions(+), 66 deletions(-) diff --git a/pype/modules/ftrack/events/event_next_task_update.py b/pype/modules/ftrack/events/event_next_task_update.py index dc1ab0a0d7..df2f3cd6ed 100644 --- a/pype/modules/ftrack/events/event_next_task_update.py +++ b/pype/modules/ftrack/events/event_next_task_update.py @@ -1,92 +1,209 @@ -import ftrack_api -from pype.modules.ftrack import BaseEvent import operator +import collections +from pype.modules.ftrack import BaseEvent class NextTaskUpdate(BaseEvent): + def filter_entities_info(self, event): + # Filter if event contain relevant data + entities_info = event["data"].get("entities") + if not entities_info: + return - def get_next_task(self, task, session): - parent = task['parent'] - # tasks = parent['tasks'] - tasks = parent['children'] + first_filtered_entities = [] + for entity_info in entities_info: + # Care only about tasks + if entity_info.get("entityType") != "task": + continue - def sort_types(types): - data = {} - for t in types: - data[t] = t.get('sort') + # Care only about changes of status + changes = entity_info.get("changes") or {} + statusid_changes = changes.get("statusid") or {} + if ( + statusid_changes.get("new") is None + or statusid_changes.get("old") is None + ): + continue - data = sorted(data.items(), key=operator.itemgetter(1)) - results = [] - for item in data: - results.append(item[0]) - return results + first_filtered_entities.append(entity_info) - types_sorted = sort_types(session.query('Type')) - next_types = None - for t in types_sorted: - if t['id'] == task['type_id']: - next_types = types_sorted[(types_sorted.index(t) + 1):] + status_ids = [ + entity_info["changes"]["statusid"]["new"] + for entity_info in first_filtered_entities + ] + statuses_by_id = self.get_statuses_by_id(status_ids=status_ids) - for nt in next_types: - for t in tasks: - if nt['id'] == t['type_id']: - return t + # Care only about tasks having status with state `Done` + filtered_entities = [] + for entity_info in first_filtered_entities: + status_id = entity_info["changes"]["statusid"]["new"] + status_entity = statuses_by_id[status_id] + if status_entity["state"]["name"].lower() == "done": + filtered_entities.append(entities_info) - return None + return filtered_entities + + def get_parents_by_id(self, session, entities_info): + parent_ids = [ + "\"{}\"".format(entity_info["parentId"]) + for entity_info in entities_info + ] + parent_entities = session.query( + "TypedContext where id in ({})".format(", ".join(parent_ids)) + ).all() + + return { + entity["id"]: entity + for entity in parent_entities + } + + def get_tasks_by_id(self, session, parent_ids): + joined_parent_ids = ",".join([ + "\"{}\"".format(parent_id) + for parent_id in parent_ids + ]) + task_entities = session.query( + "Task where parent_id in ({})".format(", ".join(joined_parent_ids)) + ).all() + + return { + entity["id"]: entity + for entity in task_entities + } + + def get_statuses_by_id(self, session, task_entities=None, status_ids=None): + if task_entities is None and status_ids is None: + return {} + + if status_ids is None: + status_ids = [] + for task_entity in task_entities: + status_ids.append(task_entities["status_id"]) + + status_entities = session.query( + "Status where id in ({})".format(", ".join(status_ids)) + ).all() + + return { + entity["id"]: entity + for entity in status_entities + } + + def get_sorted_task_types(self, session): + data = { + _type: _type.get("sort") + for _type in session.query("Type").all() + if _type.get("sort") is not None + } + + return [ + item[0] + for item in sorted(data.items(), key=operator.itemgetter(1)) + ] def launch(self, session, event): '''Propagates status from version to task when changed''' - # self.log.info(event) - # start of event procedure ---------------------------------- + entities_info = self.filter_entities_info(event) + if not entities_info: + return - for entity in event['data'].get('entities', []): - changes = entity.get('changes', None) - if changes is None: - continue - statusid_changes = changes.get('statusid', {}) - if ( - entity['entityType'] != 'task' or - 'statusid' not in (entity.get('keys') or []) or - statusid_changes.get('new', None) is None or - statusid_changes.get('old', None) is None - ): + parents_by_id = self.get_parents_by_id(session, entities_info) + tasks_by_id = self.get_tasks_by_id( + session, tuple(parents_by_id.keys()) + ) + + tasks_to_parent_id = collections.defaultdict(list) + for task_entity in tasks_by_id.values(): + tasks_to_parent_id[task_entity["parent_id"]].append(task_entity) + + statuses_by_id = self.get_statuses_by_id(session, tasks_by_id.values()) + + # Prepare all task types + sorted_task_types = self.get_sorted_task_types(session) + sorted_task_types_len = len(sorted_task_types) + + next_status_name = "Ready" + next_status = session.query( + "Status where name is \"{}\"".format(next_status_name) + ).first() + if not next_status: + self.log.warning("Couldn't find status with name \"{}\"".format( + next_status_name + )) + return + + for entity_info in entities_info: + parent_id = entities_info["parentId"] + task_id = entity_info["entityId"] + task_entity = tasks_by_id[task_id] + + all_same_type_taks_done = True + for parents_task in tasks_to_parent_id[parent_id]: + if ( + parents_task["id"] == task_id + or parents_task["type_id"] != task_entity["type_id"] + ): + continue + + parents_task_status = statuses_by_id[parents_task["status_id"]] + if parents_task_status["state"]["name"].lower() != "done": + all_same_type_taks_done = False + break + + if not all_same_type_taks_done: continue - task = session.get('Task', entity['entityId']) + from_idx = None + for idx, task_type in enumerate(sorted_task_types): + if task_type["id"] == task_entity["type_id"]: + from_idx = idx + 1 + break - status = session.get('Status', - entity['changes']['statusid']['new']) - state = status['state']['name'] + # Current task type is last in order + if from_idx >= sorted_task_types_len: + continue - next_task = self.get_next_task(task, session) + next_task_type_id = None + next_task_type_tasks = [] + for idx in range(from_idx, sorted_task_types_len): + next_task_type = sorted_task_types[idx] + for parents_task in tasks_to_parent_id[parent_id]: + if next_task_type_id is None: + if parents_task["type_id"] != next_task_type["id"]: + continue + next_task_type_id = next_task_type["id"] - # Setting next task to Ready, if on NOT READY - if next_task and state == 'Done': - if next_task['status']['name'].lower() == 'not ready': + if parents_task["type_id"] == next_task_type_id: + next_task_type_tasks.append(parents_task) - # Get path to task - path = task['name'] - for p in task['ancestors']: - path = p['name'] + '/' + path + if next_task_type_id is not None: + break - # Setting next task status - try: - query = 'Status where name is "{}"'.format('Ready') - status_to_set = session.query(query).one() - next_task['status'] = status_to_set - session.commit() - self.log.info(( - '>>> [ {} ] updated to [ Ready ]' - ).format(path)) - except Exception as e: - session.rollback() - self.log.warning(( - '!!! [ {} ] status couldnt be set: [ {} ]' - ).format(path, str(e)), exc_info=True) + for next_task_entity in next_task_type_tasks: + if next_task_entity["status"]["name"].lower() != "not ready": + continue + + ent_path = "/".join( + [ent["name"] for ent in next_task_entity["link"]] + ) + try: + next_task_entity["status"] = next_status + session.commit() + self.log.info( + "\"{}\" updated status to \"{}\"".format( + ent_path, next_status_name + ) + ) + except Exception: + session.rollback() + self.log.warning( + "\"{}\" status couldnt be set to \"{}\"".format( + ent_path, next_status_name + ), + exc_info=True + ) def register(session, plugins_presets): - '''Register plugin. Called when used as an plugin.''' - NextTaskUpdate(session, plugins_presets).register() From 151631596ad7170e24388360096cf5bbab993160 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 Aug 2020 13:23:29 +0200 Subject: [PATCH 2/5] minor fixes --- .../ftrack/events/event_next_task_update.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pype/modules/ftrack/events/event_next_task_update.py b/pype/modules/ftrack/events/event_next_task_update.py index df2f3cd6ed..57315a1fed 100644 --- a/pype/modules/ftrack/events/event_next_task_update.py +++ b/pype/modules/ftrack/events/event_next_task_update.py @@ -4,7 +4,7 @@ from pype.modules.ftrack import BaseEvent class NextTaskUpdate(BaseEvent): - def filter_entities_info(self, event): + def filter_entities_info(self, session, event): # Filter if event contain relevant data entities_info = event["data"].get("entities") if not entities_info: @@ -31,7 +31,9 @@ class NextTaskUpdate(BaseEvent): entity_info["changes"]["statusid"]["new"] for entity_info in first_filtered_entities ] - statuses_by_id = self.get_statuses_by_id(status_ids=status_ids) + statuses_by_id = self.get_statuses_by_id( + session, status_ids=status_ids + ) # Care only about tasks having status with state `Done` filtered_entities = [] @@ -39,7 +41,7 @@ class NextTaskUpdate(BaseEvent): status_id = entity_info["changes"]["statusid"]["new"] status_entity = statuses_by_id[status_id] if status_entity["state"]["name"].lower() == "done": - filtered_entities.append(entities_info) + filtered_entities.append(entity_info) return filtered_entities @@ -63,7 +65,7 @@ class NextTaskUpdate(BaseEvent): for parent_id in parent_ids ]) task_entities = session.query( - "Task where parent_id in ({})".format(", ".join(joined_parent_ids)) + "Task where parent_id in ({})".format(joined_parent_ids) ).all() return { @@ -78,7 +80,10 @@ class NextTaskUpdate(BaseEvent): if status_ids is None: status_ids = [] for task_entity in task_entities: - status_ids.append(task_entities["status_id"]) + status_ids.append(task_entity["status_id"]) + + if not status_ids: + return {} status_entities = session.query( "Status where id in ({})".format(", ".join(status_ids)) @@ -104,7 +109,7 @@ class NextTaskUpdate(BaseEvent): def launch(self, session, event): '''Propagates status from version to task when changed''' - entities_info = self.filter_entities_info(event) + entities_info = self.filter_entities_info(session, event) if not entities_info: return @@ -134,7 +139,7 @@ class NextTaskUpdate(BaseEvent): return for entity_info in entities_info: - parent_id = entities_info["parentId"] + parent_id = entity_info["parentId"] task_id = entity_info["entityId"] task_entity = tasks_by_id[task_id] From 0c67a85ce7b1558d2d236d2f3023023668282dc1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 Aug 2020 14:28:58 +0200 Subject: [PATCH 3/5] moved few steps --- pype/modules/ftrack/events/event_next_task_update.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pype/modules/ftrack/events/event_next_task_update.py b/pype/modules/ftrack/events/event_next_task_update.py index 57315a1fed..0f84ed4b44 100644 --- a/pype/modules/ftrack/events/event_next_task_update.py +++ b/pype/modules/ftrack/events/event_next_task_update.py @@ -124,10 +124,6 @@ class NextTaskUpdate(BaseEvent): statuses_by_id = self.get_statuses_by_id(session, tasks_by_id.values()) - # Prepare all task types - sorted_task_types = self.get_sorted_task_types(session) - sorted_task_types_len = len(sorted_task_types) - next_status_name = "Ready" next_status = session.query( "Status where name is \"{}\"".format(next_status_name) @@ -159,6 +155,10 @@ class NextTaskUpdate(BaseEvent): if not all_same_type_taks_done: continue + # Prepare all task types + sorted_task_types = self.get_sorted_task_types(session) + sorted_task_types_len = len(sorted_task_types) + from_idx = None for idx, task_type in enumerate(sorted_task_types): if task_type["id"] == task_entity["type_id"]: @@ -166,7 +166,7 @@ class NextTaskUpdate(BaseEvent): break # Current task type is last in order - if from_idx >= sorted_task_types_len: + if from_idx is None or from_idx >= sorted_task_types_len: continue next_task_type_id = None From ca165f6139d72938983ac4f74bf601996a1853ae Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 10 Aug 2020 16:25:23 +0200 Subject: [PATCH 4/5] blocked statuses are ignored when done statuses are checked --- pype/modules/ftrack/events/event_next_task_update.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pype/modules/ftrack/events/event_next_task_update.py b/pype/modules/ftrack/events/event_next_task_update.py index 0f84ed4b44..2df3800d8a 100644 --- a/pype/modules/ftrack/events/event_next_task_update.py +++ b/pype/modules/ftrack/events/event_next_task_update.py @@ -148,7 +148,12 @@ class NextTaskUpdate(BaseEvent): continue parents_task_status = statuses_by_id[parents_task["status_id"]] - if parents_task_status["state"]["name"].lower() != "done": + low_state_name = parents_task_status["state"]["name"].lower() + # Skip if task's status is in blocked state (e.g. Omitted) + if low_state_name != "blocked": + continue + + if low_state_name != "done": all_same_type_taks_done = False break From 0c3076c931eab668a486226f4dd23a45d8eef5ae Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 11 Aug 2020 11:30:35 +0200 Subject: [PATCH 5/5] it is not checked blocked state on parents tasks but if status name is omitted --- pype/modules/ftrack/events/event_next_task_update.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pype/modules/ftrack/events/event_next_task_update.py b/pype/modules/ftrack/events/event_next_task_update.py index 2df3800d8a..1f8407e559 100644 --- a/pype/modules/ftrack/events/event_next_task_update.py +++ b/pype/modules/ftrack/events/event_next_task_update.py @@ -148,11 +148,12 @@ class NextTaskUpdate(BaseEvent): continue parents_task_status = statuses_by_id[parents_task["status_id"]] - low_state_name = parents_task_status["state"]["name"].lower() - # Skip if task's status is in blocked state (e.g. Omitted) - if low_state_name != "blocked": + low_status_name = parents_task_status["name"].lower() + # Skip if task's status name "Omitted" + if low_status_name == "omitted": continue + low_state_name = parents_task_status["state"]["name"].lower() if low_state_name != "done": all_same_type_taks_done = False break