Merge pull request #422 from pypeclub/feature/next_task_status_update_enhance

Feature/next task status update enhance
This commit is contained in:
Milan Kolar 2020-08-11 12:04:05 +02:00 committed by GitHub
commit c57086b13d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,92 +1,220 @@
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, session, 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(
session, 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(entity_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(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_entity["status_id"])
if not status_ids:
return {}
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(session, 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())
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 = entity_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"]]
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
if not all_same_type_taks_done:
continue
task = session.get('Task', entity['entityId'])
# Prepare all task types
sorted_task_types = self.get_sorted_task_types(session)
sorted_task_types_len = len(sorted_task_types)
status = session.get('Status',
entity['changes']['statusid']['new'])
state = status['state']['name']
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
next_task = self.get_next_task(task, session)
# Current task type is last in order
if from_idx is None or from_idx >= sorted_task_types_len:
continue
# Setting next task to Ready, if on NOT READY
if next_task and state == 'Done':
if next_task['status']['name'].lower() == 'not ready':
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"]
# Get path to task
path = task['name']
for p in task['ancestors']:
path = p['name'] + '/' + path
if parents_task["type_id"] == next_task_type_id:
next_task_type_tasks.append(parents_task)
# 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)
if next_task_type_id is not None:
break
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()