From c644453b76e36abebbdabee7d259af799323004d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 18 Dec 2020 12:52:27 +0100 Subject: [PATCH 01/10] added object type filtering to settings --- pype/settings/defaults/project_settings/ftrack.json | 6 +++++- .../projects_schema/schema_project_ftrack.json | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/pype/settings/defaults/project_settings/ftrack.json b/pype/settings/defaults/project_settings/ftrack.json index 4d617b9f09..b03328115b 100644 --- a/pype/settings/defaults/project_settings/ftrack.json +++ b/pype/settings/defaults/project_settings/ftrack.json @@ -45,6 +45,10 @@ }, "status_task_to_parent": { "enabled": true, + "parent_object_types": [ + "Shot", + "Asset Build" + ], "parent_status_match_all_task_statuses": { "Completed": [ "Approved", @@ -190,4 +194,4 @@ "ftrack_custom_attributes": {} } } -} +} \ No newline at end of file diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_ftrack.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_ftrack.json index 70f578822a..a0cb6c9255 100644 --- a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_ftrack.json +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_ftrack.json @@ -157,6 +157,16 @@ "key": "enabled", "label": "Enabled" }, + { + "type": "label", + "label": "List of parent object types where this is triggered (\"Shot\", \"Asset Build\", etc.). Skipped if list is empty." + }, + { + "type": "list", + "object_type": "text", + "key": "parent_object_types", + "label": "Object types" + }, { "key": "parent_status_match_all_task_statuses", "type": "dict-modifiable", From 50d2d9db71f09b7c6afc446f302d2a430035b4cc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 18 Dec 2020 15:32:30 +0100 Subject: [PATCH 02/10] get rid of register override and class attributes --- .../events/event_task_to_parent_status.py | 59 ------------------- 1 file changed, 59 deletions(-) diff --git a/pype/modules/ftrack/events/event_task_to_parent_status.py b/pype/modules/ftrack/events/event_task_to_parent_status.py index f14c52e3a6..921aa42d4d 100644 --- a/pype/modules/ftrack/events/event_task_to_parent_status.py +++ b/pype/modules/ftrack/events/event_task_to_parent_status.py @@ -3,66 +3,7 @@ from pype.modules.ftrack import BaseEvent class TaskStatusToParent(BaseEvent): - # Parent types where we care about changing of status - parent_types = ["shot", "asset build"] - # All parent's tasks must have status name in `task_statuses` key to apply - # status name in `new_status` - parent_status_match_all_task_statuses = [ - { - "new_status": "approved", - "task_statuses": [ - "approved", "omitted" - ] - } - ] - - # Task's status was changed to something in `task_statuses` to apply - # `new_status` on it's parent - # - this is done only if `parent_status_match_all_task_statuses` filtering - # didn't found matching status - parent_status_by_task_status = [ - { - "new_status": "in progress", - "task_statuses": [ - "in progress" - ] - } - ] - - def register(self, *args, **kwargs): - result = super(TaskStatusToParent, self).register(*args, **kwargs) - # Clean up presetable attributes - _new_all_match = [] - if self.parent_status_match_all_task_statuses: - for item in self.parent_status_match_all_task_statuses: - _new_all_match.append({ - "new_status": item["new_status"].lower(), - "task_statuses": [ - status_name.lower() - for status_name in item["task_statuses"] - ] - }) - self.parent_status_match_all_task_statuses = _new_all_match - - _new_single_match = [] - if self.parent_status_by_task_status: - for item in self.parent_status_by_task_status: - _new_single_match.append({ - "new_status": item["new_status"].lower(), - "task_statuses": [ - status_name.lower() - for status_name in item["task_statuses"] - ] - }) - self.parent_status_by_task_status = _new_single_match - - self.parent_types = [ - parent_type.lower() - for parent_type in self.parent_types - ] - - return result def filter_entities_info(self, session, event): # Filter if event contain relevant data From 3031224736c9ed1ae1eab2b52f817c537ee6f235 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 18 Dec 2020 15:33:26 +0100 Subject: [PATCH 03/10] task to parent status are converted to use settings --- .../events/event_task_to_parent_status.py | 392 +++++++++++------- 1 file changed, 251 insertions(+), 141 deletions(-) diff --git a/pype/modules/ftrack/events/event_task_to_parent_status.py b/pype/modules/ftrack/events/event_task_to_parent_status.py index 921aa42d4d..4498456d03 100644 --- a/pype/modules/ftrack/events/event_task_to_parent_status.py +++ b/pype/modules/ftrack/events/event_task_to_parent_status.py @@ -3,49 +3,47 @@ from pype.modules.ftrack import BaseEvent class TaskStatusToParent(BaseEvent): + settings_key = "status_task_to_parent" - - def filter_entities_info(self, session, event): + def filter_entities_info(self, event): # Filter if event contain relevant data entities_info = event["data"].get("entities") if not entities_info: return - filtered_entities = [] + filtered_entity_info = collections.defaultdict(list) + status_ids = set() for entity_info in entities_info: # Care only about tasks if entity_info.get("entityType") != "task": continue # Care only about changes of status - changes = entity_info.get("changes") or {} - statusid_changes = changes.get("statusid") or {} + changes = entity_info.get("changes") + if not changes: + continue + statusid_changes = changes.get("statusid") + if not statusid_changes: + continue + + new_status_id = entity_info["changes"]["statusid"]["new"] if ( - statusid_changes.get("new") is None - or statusid_changes.get("old") is None + statusid_changes.get("old") is None + or new_status_id is None ): continue - filtered_entities.append(entity_info) + project_id = None + for parent_item in reversed(entity_info["parents"]): + if parent_item["entityType"] == "show": + project_id = parent_item["entityId"] + break - if not filtered_entities: - return + if project_id: + filtered_entity_info[project_id].append(entity_info) + status_ids.add(new_status_id) - status_ids = [ - entity_info["changes"]["statusid"]["new"] - for entity_info in filtered_entities - ] - statuses_by_id = self.get_statuses_by_id( - session, status_ids=status_ids - ) - - # Care only about tasks having status with state `Done` - output = [] - for entity_info in filtered_entities: - status_id = entity_info["changes"]["statusid"]["new"] - entity_info["status_entity"] = statuses_by_id[status_id] - output.append(entity_info) - return output + return filtered_entity_info def get_parents_by_id(self, session, entities_info, object_types): task_type_id = None @@ -91,146 +89,216 @@ class TaskStatusToParent(BaseEvent): 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 {} + def launch(self, session, event): + '''Propagates status from version to task when changed''' - if status_ids is None: - status_ids = [] - for task_entity in task_entities: - status_ids.append(task_entity["status_id"]) + filtered_entities_info = self.filter_entities_info(event) + if not filtered_entities_info: + return - if not status_ids: - return {} + for project_id, entities_info in filtered_entities_info.items(): + self.process_by_project(session, event, project_id, entities_info) - status_entities = session.query( - "Status where id in ({})".format(", ".join(status_ids)) + def process_by_project(self, session, event, project_id, entities_info): + # Get project entity + project_entity = self.get_project_entity_from_event( + session, event, project_id + ) + # Load settings + project_settings = self.get_settings_for_project( + session, event, project_entity=project_entity + ) + + # Prepare loaded settings and check if can be processed + project_name = project_entity["full_name"] + event_settings = ( + project_settings["ftrack"]["events"][self.settings_key] + ) + result = self.prepare_settings(event_settings, project_name) + if not result: + return + + # Unpack the result + parent_object_types, all_match, single_match = result + + # Prepare valid object type ids for object types from settings + object_types = session.query("select id, name from ObjectType").all() + object_type_id_by_low_name = { + object_type["name"].lower(): object_type["id"] + for object_type in object_types + } + + valid_object_type_ids = set() + for object_type_name in parent_object_types: + if object_type_name in object_type_id_by_low_name: + valid_object_type_ids.add( + object_type_id_by_low_name[object_type_name] + ) + else: + self.log.warning( + "Unknown object type \"{}\" set on project \"{}\".".format( + object_type_name, project_name + ) + ) + + if not valid_object_type_ids: + return + + # Prepare parent ids + parent_ids = set() + for entity_info in entities_info: + parent_id = entity_info["parentId"] + if parent_id: + parent_ids.add(parent_id) + + # Query parent ids by object type ids and parent ids + parent_entities = session.query( + ( + "select id, status_id, object_type_id, link from TypedContext" + " where id in ({}) and object_type_id in ({})" + ).format( + self.join_query_keys(parent_ids), + self.join_query_keys(valid_object_type_ids) + ) + ).all() + # Skip if none of parents match the filtering + if not parent_entities: + return + + obj_ids = set() + for entity in parent_entities: + obj_ids.add(entity["object_type_id"]) + + object_type_name_by_id = { + object_type["id"]: object_type["name"] + for object_type in object_types + } + + project_schema = project_entity["project_schema"] + available_statuses_by_obj_id = {} + for obj_id in obj_ids: + obj_name = object_type_name_by_id[obj_id] + statuses = project_schema.get_statuses(obj_name) + statuses_by_low_name = { + status["name"].lower(): status + for status in statuses + } + valid = False + for name in all_match.keys(): + if name in statuses_by_low_name: + valid = True + break + + if not valid: + for name in single_match.keys(): + if name in statuses_by_low_name: + valid = True + break + if valid: + available_statuses_by_obj_id[obj_id] = statuses_by_low_name + + valid_parent_ids = set() + status_ids = set() + valid_parent_entities = [] + for entity in parent_entities: + if entity["object_type_id"] not in available_statuses_by_obj_id: + continue + + valid_parent_entities.append(entity) + valid_parent_ids.add(entity["id"]) + status_ids.add(entity["status_id"]) + + task_entities = session.query( + ( + "select id, parent_id, status_id from TypedContext" + " where parent_id in ({}) and object_type_id is \"{}\"" + ).format( + self.join_query_keys(valid_parent_ids), + object_type_id_by_low_name["task"] + ) ).all() - return { + # This should not happen but it is safer + if not task_entities: + return + + task_entities_by_parent_id = collections.defaultdict(list) + for task_entity in task_entities: + status_ids.add(task_entity["status_id"]) + parent_id = task_entity["parent_id"] + task_entities_by_parent_id[parent_id].append(task_entity) + + status_entities = session.query(( + "select id, name from Status where id in ({})" + ).format(self.join_query_keys(status_ids))).all() + + statuses_by_id = { entity["id"]: entity for entity in status_entities } - def launch(self, session, event): - '''Propagates status from version to task when changed''' - - entities_info = self.filter_entities_info(session, event) - if not entities_info: - return - - object_types = session.query("select id, name from ObjectType").all() - parents_by_id = self.get_parents_by_id( - session, entities_info, object_types - ) - if not parents_by_id: - return - tasks_by_id = self.get_tasks_by_id( - session, tuple(parents_by_id.keys()) - ) - - # Just collect them in one variable - entities_by_id = {} - for entity_id, entity in parents_by_id.items(): - entities_by_id[entity_id] = entity - for entity_id, entity in tasks_by_id.items(): - entities_by_id[entity_id] = entity - - # Map task entities by their parents - tasks_by_parent_id = collections.defaultdict(list) - for task_entity in tasks_by_id.values(): - tasks_by_parent_id[task_entity["parent_id"]].append(task_entity) - - # Found status entities for all queried entities - statuses_by_id = self.get_statuses_by_id( - session, - entities_by_id.values() - ) - # New status determination logic new_statuses_by_parent_id = self.new_status_by_all_task_statuses( - parents_by_id.keys(), tasks_by_parent_id, statuses_by_id + task_entities_by_parent_id, statuses_by_id, all_match ) + task_entities_by_id = { + task_entity["id"]: task_entity + for task_entity in task_entities + } # Check if there are remaining any parents that does not have # determined new status yet remainder_tasks_by_parent_id = collections.defaultdict(list) for entity_info in entities_info: + entity_id = entity_info["entityId"] + if entity_id not in task_entities_by_id: + continue parent_id = entity_info["parentId"] if ( # Skip if already has determined new status parent_id in new_statuses_by_parent_id # Skip if parent is not in parent mapping # - if was not found or parent type is not interesting - or parent_id not in parents_by_id + or parent_id not in task_entities_by_parent_id ): continue remainder_tasks_by_parent_id[parent_id].append( - entities_by_id[entity_info["entityId"]] + task_entities_by_id[entity_id] ) # Try to find new status for remained parents new_statuses_by_parent_id.update( self.new_status_by_remainders( remainder_tasks_by_parent_id, - statuses_by_id + statuses_by_id, + single_match ) ) - # Make sure new_status is set to valid value - for parent_id in tuple(new_statuses_by_parent_id.keys()): - new_status_name = new_statuses_by_parent_id[parent_id] - if not new_status_name: - new_statuses_by_parent_id.pop(parent_id) - # If there are not new statuses then just skip if not new_statuses_by_parent_id: return - # Get project schema from any available entity - _entity = None - for _ent in entities_by_id.values(): - _entity = _ent - break - - project_entity = self.get_project_from_entity(_entity) - project_schema = project_entity["project_schema"] - - # Map type names by lowere type names - types_mapping = { - _type.lower(): _type - for _type in session.types + parent_entities_by_id = { + parent_entity["id"]: parent_entity + for parent_entity in valid_parent_entities } - # Map object type id by lowered and modified object type name - object_type_mapping = {} - for object_type in object_types: - mapping_name = object_type["name"].lower().replace(" ", "") - object_type_mapping[object_type["id"]] = mapping_name - - statuses_by_obj_id = {} for parent_id, new_status_name in new_statuses_by_parent_id.items(): if not new_status_name: continue - parent_entity = entities_by_id[parent_id] - obj_id = parent_entity["object_type_id"] - # Find statuses for entity type by object type name - # in project's schema and cache them - if obj_id not in statuses_by_obj_id: - mapping_name = object_type_mapping[obj_id] - mapped_name = types_mapping.get(mapping_name) - statuses = project_schema.get_statuses(mapped_name) - statuses_by_obj_id[obj_id] = { - status["name"].lower(): status - for status in statuses - } - - statuses_by_name = statuses_by_obj_id[obj_id] - new_status = statuses_by_name.get(new_status_name) + parent_entity = parent_entities_by_id[parent_id] ent_path = "/".join( [ent["name"] for ent in parent_entity["link"]] ) + + obj_id = parent_entity["object_type_id"] + statuses_by_low_name = available_statuses_by_obj_id.get(obj_id) + if not statuses_by_low_name: + continue + + new_status = statuses_by_low_name.get(new_status_name) if not new_status: self.log.warning(( "\"{}\" Couldn't change status to \"{}\"." @@ -240,18 +308,18 @@ class TaskStatusToParent(BaseEvent): )) continue - current_status_name = parent_entity["status"]["name"] + current_status = parent_entity["status"] # Do nothing if status is already set - if new_status["name"] == current_status_name: + if new_status["id"] == current_status["id"]: self.log.debug( "\"{}\" Status \"{}\" already set.".format( - ent_path, current_status_name + ent_path, current_status["name"] ) ) continue try: - parent_entity["status"] = new_status + parent_entity["status_id"] = new_status["id"] session.commit() self.log.info( "\"{}\" changed status to \"{}\"".format( @@ -267,8 +335,56 @@ class TaskStatusToParent(BaseEvent): exc_info=True ) + def prepare_settings(self, event_settings, project_name): + if not event_settings["enabled"]: + self.log.debug("Project \"{}\" has disabled {}.".format( + project_name, self.__class__.__name__ + )) + return + + _parent_object_types = event_settings["parent_object_types"] + if not _parent_object_types: + self.log.debug(( + "Project \"{}\" does not have set" + " parent object types filtering." + ).format(project_name)) + return + + _all_match = ( + event_settings["parent_status_match_all_task_statuses"] + ) + _single_match = ( + event_settings["parent_status_by_task_status"] + ) + + if not _all_match and not _single_match: + self.log.debug(( + "Project \"{}\" does not have set" + " parent status mappings." + ).format(project_name)) + return + + parent_object_types = [ + item.lower() + for item in _parent_object_types + ] + all_match = {} + for new_status_name, task_status_names in _all_match.items(): + all_match[new_status_name.lower] = [ + status_name.lower() + for status_name in task_status_names + ] + + single_match = {} + for new_status_name, task_status_names in _single_match.items(): + single_match[new_status_name.lower] = [ + status_name.lower() + for status_name in task_status_names + ] + return parent_object_types, all_match, single_match + def new_status_by_all_task_statuses( - self, parent_ids, tasks_by_parent_id, statuses_by_id + self, tasks_by_parent_id, statuses_by_id, all_match ): """All statuses of parent entity must match specific status names. @@ -276,23 +392,23 @@ class TaskStatusToParent(BaseEvent): determined. """ output = {} - for parent_id in parent_ids: + for parent_id, task_entities in tasks_by_parent_id.items(): task_statuses_lowered = set() - for task_entity in tasks_by_parent_id[parent_id]: + for task_entity in task_entities: task_status = statuses_by_id[task_entity["status_id"]] low_status_name = task_status["name"].lower() task_statuses_lowered.add(low_status_name) new_status = None - for item in self.parent_status_match_all_task_statuses: + for _new_status, task_statuses in all_match: valid_item = True for status_name_low in task_statuses_lowered: - if status_name_low not in item["task_statuses"]: + if status_name_low not in task_statuses: valid_item = False break if valid_item: - new_status = item["new_status"] + new_status = _new_status break if new_status is not None: @@ -301,7 +417,7 @@ class TaskStatusToParent(BaseEvent): return output def new_status_by_remainders( - self, remainder_tasks_by_parent_id, statuses_by_id + self, remainder_tasks_by_parent_id, statuses_by_id, single_match ): """By new task status can be determined new status of parent.""" output = {} @@ -312,27 +428,21 @@ class TaskStatusToParent(BaseEvent): if not task_entities: continue + # TODO re-implement status orders # For cases there are multiple tasks in changes # - task status which match any new status item by order in the # list `parent_status_by_task_status` is preffered - best_order = len(self.parent_status_by_task_status) - best_order_status = None + new_status = None for task_entity in task_entities: task_status = statuses_by_id[task_entity["status_id"]] low_status_name = task_status["name"].lower() - for order, item in enumerate( - self.parent_status_by_task_status - ): - if order >= best_order: + for _new_status, task_statuses in single_match.items(): + if low_status_name in task_statuses: + new_status = _new_status break - if low_status_name in item["task_statuses"]: - best_order = order - best_order_status = item["new_status"] - break - - if best_order_status: - output[parent_id] = best_order_status + if new_status: + output[parent_id] = new_status return output From cfcb9f49bd2ed738fb8cd46557879ab948fbc0f2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 18 Dec 2020 16:03:47 +0100 Subject: [PATCH 04/10] removed unused methods --- .../events/event_task_to_parent_status.py | 64 +++---------------- 1 file changed, 10 insertions(+), 54 deletions(-) diff --git a/pype/modules/ftrack/events/event_task_to_parent_status.py b/pype/modules/ftrack/events/event_task_to_parent_status.py index 4498456d03..a0b7c12408 100644 --- a/pype/modules/ftrack/events/event_task_to_parent_status.py +++ b/pype/modules/ftrack/events/event_task_to_parent_status.py @@ -5,6 +5,16 @@ from pype.modules.ftrack import BaseEvent class TaskStatusToParent(BaseEvent): settings_key = "status_task_to_parent" + def launch(self, session, event): + """Propagates status from task to parent when changed.""" + + filtered_entities_info = self.filter_entities_info(event) + if not filtered_entities_info: + return + + for project_id, entities_info in filtered_entities_info.items(): + self.process_by_project(session, event, project_id, entities_info) + def filter_entities_info(self, event): # Filter if event contain relevant data entities_info = event["data"].get("entities") @@ -45,60 +55,6 @@ class TaskStatusToParent(BaseEvent): return filtered_entity_info - def get_parents_by_id(self, session, entities_info, object_types): - task_type_id = None - valid_object_type_ids = [] - for object_type in object_types: - object_name_low = object_type["name"].lower() - if object_name_low == "task": - task_type_id = object_type["id"] - - if object_name_low in self.parent_types: - valid_object_type_ids.append(object_type["id"]) - - parent_ids = [ - "\"{}\"".format(entity_info["parentId"]) - for entity_info in entities_info - if entity_info["objectTypeId"] == task_type_id - ] - if not parent_ids: - return {} - - parent_entities = session.query(( - "TypedContext where id in ({}) and object_type_id in ({})" - ).format( - ", ".join(parent_ids), ", ".join(valid_object_type_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(joined_parent_ids) - ).all() - - return { - entity["id"]: entity - for entity in task_entities - } - - def launch(self, session, event): - '''Propagates status from version to task when changed''' - - filtered_entities_info = self.filter_entities_info(event) - if not filtered_entities_info: - return - - for project_id, entities_info in filtered_entities_info.items(): - self.process_by_project(session, event, project_id, entities_info) - def process_by_project(self, session, event, project_id, entities_info): # Get project entity project_entity = self.get_project_entity_from_event( From ab97f5df3c5be0292ce73ea2b197036cd6548d15 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 18 Dec 2020 16:03:57 +0100 Subject: [PATCH 05/10] minor fixes --- .../modules/ftrack/events/event_task_to_parent_status.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pype/modules/ftrack/events/event_task_to_parent_status.py b/pype/modules/ftrack/events/event_task_to_parent_status.py index a0b7c12408..159c8ffd1c 100644 --- a/pype/modules/ftrack/events/event_task_to_parent_status.py +++ b/pype/modules/ftrack/events/event_task_to_parent_status.py @@ -164,6 +164,9 @@ class TaskStatusToParent(BaseEvent): valid_parent_ids.add(entity["id"]) status_ids.add(entity["status_id"]) + if not valid_parent_ids: + return + task_entities = session.query( ( "select id, parent_id, status_id from TypedContext" @@ -326,14 +329,14 @@ class TaskStatusToParent(BaseEvent): ] all_match = {} for new_status_name, task_status_names in _all_match.items(): - all_match[new_status_name.lower] = [ + all_match[new_status_name.lower()] = [ status_name.lower() for status_name in task_status_names ] single_match = {} for new_status_name, task_status_names in _single_match.items(): - single_match[new_status_name.lower] = [ + single_match[new_status_name.lower()] = [ status_name.lower() for status_name in task_status_names ] @@ -356,7 +359,7 @@ class TaskStatusToParent(BaseEvent): task_statuses_lowered.add(low_status_name) new_status = None - for _new_status, task_statuses in all_match: + for _new_status, task_statuses in all_match.items(): valid_item = True for status_name_low in task_statuses_lowered: if status_name_low not in task_statuses: From 2e6b3729067b3a3d37949f1ac9a878b650291ca1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 18 Dec 2020 16:45:07 +0100 Subject: [PATCH 06/10] `parent_status_by_task_status` is list of dictionaries --- .../defaults/project_settings/ftrack.json | 21 +++++++++++-------- .../schema_project_ftrack.json | 21 ++++++++++++++++--- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/pype/settings/defaults/project_settings/ftrack.json b/pype/settings/defaults/project_settings/ftrack.json index b03328115b..7bc935e3c5 100644 --- a/pype/settings/defaults/project_settings/ftrack.json +++ b/pype/settings/defaults/project_settings/ftrack.json @@ -55,14 +55,17 @@ "Omitted" ] }, - "parent_status_by_task_status": { - "In Progress": [ - "in progress", - "change requested", - "retake", - "pending review" - ] - } + "parent_status_by_task_status": [ + { + "new_status": "In Progress", + "task_statuses": [ + "in progress", + "change requested", + "retake", + "pending review" + ] + } + ] }, "status_task_to_version": { "enabled": true, @@ -194,4 +197,4 @@ "ftrack_custom_attributes": {} } } -} \ No newline at end of file +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_ftrack.json b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_ftrack.json index a0cb6c9255..c94ed40511 100644 --- a/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_ftrack.json +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/schema_project_ftrack.json @@ -177,12 +177,27 @@ } }, { + "type": "list", "key": "parent_status_by_task_status", - "type": "dict-modifiable", "label": "Change parent status if a single task matches", "object_type": { - "type": "list", - "object_type": "text" + "type": "dict", + "children": [ + { + "type": "text", + "label": "New parent status", + "key": "new_status" + }, + { + "type": "separator" + }, + { + "type": "list", + "label": "Task status", + "key": "task_statuses", + "object_type": "text" + } + ] } } ] From 6b7933895f338b5ba5b4bb78d54c29d53303e5b1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 18 Dec 2020 16:45:37 +0100 Subject: [PATCH 07/10] reimplemented best order status --- .../events/event_task_to_parent_status.py | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/pype/modules/ftrack/events/event_task_to_parent_status.py b/pype/modules/ftrack/events/event_task_to_parent_status.py index 159c8ffd1c..b84994aeb6 100644 --- a/pype/modules/ftrack/events/event_task_to_parent_status.py +++ b/pype/modules/ftrack/events/event_task_to_parent_status.py @@ -334,12 +334,15 @@ class TaskStatusToParent(BaseEvent): for status_name in task_status_names ] - single_match = {} - for new_status_name, task_status_names in _single_match.items(): - single_match[new_status_name.lower()] = [ - status_name.lower() - for status_name in task_status_names - ] + single_match = [] + for item in _single_match.items(): + single_match.append({ + "new_status": item["new_status"].lower(), + "task_statuses": [ + status_name.lower() + for status_name in item["task_status_names"] + ] + }) return parent_object_types, all_match, single_match def new_status_by_all_task_statuses( @@ -387,21 +390,25 @@ class TaskStatusToParent(BaseEvent): if not task_entities: continue - # TODO re-implement status orders # For cases there are multiple tasks in changes # - task status which match any new status item by order in the # list `parent_status_by_task_status` is preffered - new_status = None + best_order = len(self.parent_status_by_task_status) + best_order_status = None for task_entity in task_entities: task_status = statuses_by_id[task_entity["status_id"]] low_status_name = task_status["name"].lower() - for _new_status, task_statuses in single_match.items(): - if low_status_name in task_statuses: - new_status = _new_status + for order, item in enumerate(single_match): + if order >= best_order: break - if new_status: - output[parent_id] = new_status + if low_status_name in item["task_statuses"]: + best_order = order + best_order_status = item["new_status"] + break + + if best_order_status: + output[parent_id] = best_order_status return output From 21b2f3e01898f791c63f33b8acc40a243718b973 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 18 Dec 2020 16:52:13 +0100 Subject: [PATCH 08/10] single_match as list fix --- .../ftrack/events/event_task_to_parent_status.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pype/modules/ftrack/events/event_task_to_parent_status.py b/pype/modules/ftrack/events/event_task_to_parent_status.py index b84994aeb6..afe00e2d0c 100644 --- a/pype/modules/ftrack/events/event_task_to_parent_status.py +++ b/pype/modules/ftrack/events/event_task_to_parent_status.py @@ -67,10 +67,7 @@ class TaskStatusToParent(BaseEvent): # Prepare loaded settings and check if can be processed project_name = project_entity["full_name"] - event_settings = ( - project_settings["ftrack"]["events"][self.settings_key] - ) - result = self.prepare_settings(event_settings, project_name) + result = self.prepare_settings(project_settings, project_name) if not result: return @@ -146,8 +143,8 @@ class TaskStatusToParent(BaseEvent): break if not valid: - for name in single_match.keys(): - if name in statuses_by_low_name: + for item in single_match: + if item["new_status"] in statuses_by_low_name: valid = True break if valid: @@ -294,7 +291,11 @@ class TaskStatusToParent(BaseEvent): exc_info=True ) - def prepare_settings(self, event_settings, project_name): + def prepare_settings(self, project_settings, project_name): + event_settings = ( + project_settings["ftrack"]["events"][self.settings_key] + ) + if not event_settings["enabled"]: self.log.debug("Project \"{}\" has disabled {}.".format( project_name, self.__class__.__name__ From 8ffbe212f31e426e750626e8fe027366f23af6f3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 18 Dec 2020 18:38:40 +0100 Subject: [PATCH 09/10] single task status match fixes --- .../ftrack/events/event_task_to_parent_status.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pype/modules/ftrack/events/event_task_to_parent_status.py b/pype/modules/ftrack/events/event_task_to_parent_status.py index afe00e2d0c..42d3ca0877 100644 --- a/pype/modules/ftrack/events/event_task_to_parent_status.py +++ b/pype/modules/ftrack/events/event_task_to_parent_status.py @@ -329,19 +329,19 @@ class TaskStatusToParent(BaseEvent): for item in _parent_object_types ] all_match = {} - for new_status_name, task_status_names in _all_match.items(): + for new_status_name, task_statuses in _all_match.items(): all_match[new_status_name.lower()] = [ status_name.lower() - for status_name in task_status_names + for status_name in task_statuses ] single_match = [] - for item in _single_match.items(): + for item in _single_match: single_match.append({ "new_status": item["new_status"].lower(), "task_statuses": [ status_name.lower() - for status_name in item["task_status_names"] + for status_name in item["task_statuses"] ] }) return parent_object_types, all_match, single_match @@ -393,8 +393,8 @@ class TaskStatusToParent(BaseEvent): # For cases there are multiple tasks in changes # - task status which match any new status item by order in the - # list `parent_status_by_task_status` is preffered - best_order = len(self.parent_status_by_task_status) + # list `single_match` is preffered + best_order = len(single_match) best_order_status = None for task_entity in task_entities: task_status = statuses_by_id[task_entity["status_id"]] From 22d783282e4b34c71596c39eb97e12b978e13748 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 18 Dec 2020 19:13:46 +0100 Subject: [PATCH 10/10] fix entity type mapping --- .../ftrack/events/event_task_to_parent_status.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pype/modules/ftrack/events/event_task_to_parent_status.py b/pype/modules/ftrack/events/event_task_to_parent_status.py index 42d3ca0877..4620f84395 100644 --- a/pype/modules/ftrack/events/event_task_to_parent_status.py +++ b/pype/modules/ftrack/events/event_task_to_parent_status.py @@ -122,10 +122,16 @@ class TaskStatusToParent(BaseEvent): for entity in parent_entities: obj_ids.add(entity["object_type_id"]) - object_type_name_by_id = { - object_type["id"]: object_type["name"] - for object_type in object_types + types_mapping = { + _type.lower(): _type + for _type in session.types } + # Map object type id by lowered and modified object type name + object_type_name_by_id = {} + for object_type in object_types: + mapping_name = object_type["name"].lower().replace(" ", "") + obj_id = object_type["id"] + object_type_name_by_id[obj_id] = types_mapping[mapping_name] project_schema = project_entity["project_schema"] available_statuses_by_obj_id = {}