mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-26 22:02:15 +01:00
Merge pull request #821 from pypeclub/feature/version_to_task_with_right_settings
Version to task with right settings
This commit is contained in:
commit
c2a3d20667
10 changed files with 289 additions and 73 deletions
|
|
@ -1,25 +1,40 @@
|
|||
from pype.modules.ftrack import BaseEvent
|
||||
from pype.api import get_project_settings
|
||||
|
||||
|
||||
class VersionToTaskStatus(BaseEvent):
|
||||
"""Propagates status from version to task when changed."""
|
||||
def launch(self, session, event):
|
||||
'''Propagates status from version to task when changed'''
|
||||
# Filter event entities
|
||||
# - output is dictionary where key is project id and event info in
|
||||
# value
|
||||
filtered_entities_info = self.filter_entity_info(event)
|
||||
if not filtered_entities_info:
|
||||
return
|
||||
|
||||
# start of event procedure ----------------------------------
|
||||
for entity in event['data'].get('entities', []):
|
||||
for project_id, entities_info in filtered_entities_info.items():
|
||||
self.process_by_project(session, event, project_id, entities_info)
|
||||
|
||||
# TODO remove `join_query_keys` as it should be in `BaseHandler`
|
||||
@staticmethod
|
||||
def join_query_keys(keys):
|
||||
"""Helper to join keys to query."""
|
||||
return ",".join(["\"{}\"".format(key) for key in keys])
|
||||
|
||||
def filter_entity_info(self, event):
|
||||
filtered_entity_info = {}
|
||||
for entity_info in event["data"].get("entities", []):
|
||||
# Filter AssetVersions
|
||||
if entity["entityType"] != "assetversion":
|
||||
if entity_info["entityType"] != "assetversion":
|
||||
continue
|
||||
|
||||
# Skip if statusid not in keys (in changes)
|
||||
keys = entity.get("keys")
|
||||
keys = entity_info.get("keys")
|
||||
if not keys or "statusid" not in keys:
|
||||
continue
|
||||
|
||||
# Get new version task name
|
||||
version_status_id = (
|
||||
entity
|
||||
entity_info
|
||||
.get("changes", {})
|
||||
.get("statusid", {})
|
||||
.get("new", {})
|
||||
|
|
@ -29,74 +44,162 @@ class VersionToTaskStatus(BaseEvent):
|
|||
if not version_status_id:
|
||||
continue
|
||||
|
||||
try:
|
||||
version_status = session.get("Status", version_status_id)
|
||||
except Exception:
|
||||
self.log.warning(
|
||||
"Troubles with query status id [ {} ]".format(
|
||||
version_status_id
|
||||
),
|
||||
exc_info=True
|
||||
# Get project id from entity info
|
||||
project_id = entity_info["parents"][-1]["entityId"]
|
||||
if project_id not in filtered_entity_info:
|
||||
filtered_entity_info[project_id] = []
|
||||
filtered_entity_info[project_id].append(entity_info)
|
||||
return filtered_entity_info
|
||||
|
||||
def process_by_project(self, session, event, project_id, entities_info):
|
||||
# Check for project data if event is enabled for event handler
|
||||
status_mapping = None
|
||||
project_entity = self.get_project_entity_from_event(
|
||||
session, event, project_id
|
||||
)
|
||||
project_settings = self.get_settings_for_project(
|
||||
session, event, project_entity=project_entity
|
||||
)
|
||||
|
||||
project_name = project_entity["full_name"]
|
||||
# Load status mapping from presets
|
||||
event_settings = (
|
||||
project_settings["ftrack"]["events"]["status_version_to_task"]
|
||||
)
|
||||
# Skip if event is not enabled or status mapping is not set
|
||||
if not event_settings["enabled"]:
|
||||
self.log.debug("Project \"{}\" has disabled {}".format(
|
||||
project_name, self.__class__.__name__
|
||||
))
|
||||
return
|
||||
|
||||
_status_mapping = event_settings["mapping"]
|
||||
if not _status_mapping:
|
||||
self.log.debug(
|
||||
"Project \"{}\" does not have set mapping for {}".format(
|
||||
project_name, self.__class__.__name__
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
if not version_status:
|
||||
status_mapping = {
|
||||
key.lower(): value
|
||||
for key, value in _status_mapping.items()
|
||||
}
|
||||
|
||||
asset_types_to_skip = [
|
||||
short_name.lower()
|
||||
for short_name in event_settings["asset_types_to_skip"]
|
||||
]
|
||||
|
||||
# Collect entity ids
|
||||
asset_version_ids = set()
|
||||
for entity_info in entities_info:
|
||||
asset_version_ids.add(entity_info["entityId"])
|
||||
|
||||
# Query tasks for AssetVersions
|
||||
_asset_version_entities = session.query(
|
||||
"AssetVersion where task_id != none and id in ({})".format(
|
||||
self.join_query_keys(asset_version_ids)
|
||||
)
|
||||
).all()
|
||||
if not _asset_version_entities:
|
||||
return
|
||||
|
||||
# Filter asset versions by asset type and store their task_ids
|
||||
task_ids = set()
|
||||
asset_version_entities = []
|
||||
for asset_version in _asset_version_entities:
|
||||
if asset_types_to_skip:
|
||||
short_name = asset_version["asset"]["type"]["short"].lower()
|
||||
if short_name in asset_types_to_skip:
|
||||
continue
|
||||
asset_version_entities.append(asset_version)
|
||||
task_ids.add(asset_version["task_id"])
|
||||
|
||||
# Skipt if `task_ids` are empty
|
||||
if not task_ids:
|
||||
return
|
||||
|
||||
task_entities = session.query(
|
||||
"select link from Task where id in ({})".format(
|
||||
self.join_query_keys(task_ids)
|
||||
)
|
||||
).all()
|
||||
task_entities_by_id = {
|
||||
task_entiy["id"]: task_entiy
|
||||
for task_entiy in task_entities
|
||||
}
|
||||
|
||||
# Prepare asset version by their id
|
||||
asset_versions_by_id = {
|
||||
asset_version["id"]: asset_version
|
||||
for asset_version in asset_version_entities
|
||||
}
|
||||
|
||||
# Query status entities
|
||||
status_ids = set()
|
||||
for entity_info in entities_info:
|
||||
# Skip statuses of asset versions without task
|
||||
if entity_info["entityId"] not in asset_versions_by_id:
|
||||
continue
|
||||
status_ids.add(entity_info["changes"]["statusid"]["new"])
|
||||
|
||||
version_status_orig = version_status["name"]
|
||||
version_status_entities = session.query(
|
||||
"select id, name from Status where id in ({})".format(
|
||||
self.join_query_keys(status_ids)
|
||||
)
|
||||
).all()
|
||||
|
||||
# Get entities necessary for processing
|
||||
version = session.get("AssetVersion", entity["entityId"])
|
||||
task = version.get("task")
|
||||
if not task:
|
||||
continue
|
||||
|
||||
project_entity = self.get_project_from_entity(task)
|
||||
project_name = project_entity["full_name"]
|
||||
project_settings = get_project_settings(project_name)
|
||||
|
||||
# Load status mapping from presets
|
||||
status_mapping = (
|
||||
project_settings["ftrack"]["events"]["status_version_to_task"])
|
||||
# Skip if mapping is empty
|
||||
if not status_mapping:
|
||||
# Qeury statuses
|
||||
statusese_by_obj_id = self.statuses_for_tasks(
|
||||
session, task_entities, project_entity
|
||||
)
|
||||
# Prepare status names by their ids
|
||||
status_name_by_id = {
|
||||
status_entity["id"]: status_entity["name"]
|
||||
for status_entity in version_status_entities
|
||||
}
|
||||
for entity_info in entities_info:
|
||||
entity_id = entity_info["entityId"]
|
||||
status_id = entity_info["changes"]["statusid"]["new"]
|
||||
status_name = status_name_by_id.get(status_id)
|
||||
if not status_name:
|
||||
continue
|
||||
status_name_low = status_name.lower()
|
||||
|
||||
# Lower version status name and check if has mapping
|
||||
version_status = version_status_orig.lower()
|
||||
new_status_names = []
|
||||
mapped = status_mapping.get(version_status)
|
||||
mapped = status_mapping.get(status_name_low)
|
||||
if mapped:
|
||||
new_status_names.extend(list(mapped))
|
||||
|
||||
new_status_names.append(version_status)
|
||||
new_status_names.append(status_name_low)
|
||||
|
||||
self.log.debug(
|
||||
"Processing AssetVersion status change: [ {} ]".format(
|
||||
version_status_orig
|
||||
status_name
|
||||
)
|
||||
)
|
||||
|
||||
asset_version = asset_versions_by_id[entity_id]
|
||||
task_entity = task_entities_by_id[asset_version["task_id"]]
|
||||
type_id = task_entity["type_id"]
|
||||
|
||||
# Lower all names from presets
|
||||
new_status_names = [name.lower() for name in new_status_names]
|
||||
|
||||
if version["asset"]["type"]["short"].lower() == "scene":
|
||||
continue
|
||||
|
||||
project_schema = project_entity["project_schema"]
|
||||
# Get all available statuses for Task
|
||||
statuses = project_schema.get_statuses("Task", task["type_id"])
|
||||
# map lowered status name with it's object
|
||||
stat_names_low = {
|
||||
status["name"].lower(): status for status in statuses
|
||||
}
|
||||
task_statuses_by_low_name = statusese_by_obj_id[type_id]
|
||||
|
||||
new_status = None
|
||||
for status_name in new_status_names:
|
||||
if status_name not in stat_names_low:
|
||||
if status_name not in task_statuses_by_low_name:
|
||||
self.log.debug((
|
||||
"Task does not have status name \"{}\" available."
|
||||
).format(status_name))
|
||||
continue
|
||||
|
||||
# store object of found status
|
||||
new_status = stat_names_low[status_name]
|
||||
new_status = task_statuses_by_low_name[status_name]
|
||||
self.log.debug("Status to set: [ {} ]".format(
|
||||
new_status["name"]
|
||||
))
|
||||
|
|
@ -110,16 +213,15 @@ class VersionToTaskStatus(BaseEvent):
|
|||
)
|
||||
)
|
||||
continue
|
||||
|
||||
# Get full path to task for logging
|
||||
ent_path = "/".join([ent["name"] for ent in task["link"]])
|
||||
ent_path = "/".join([ent["name"] for ent in task_entity["link"]])
|
||||
|
||||
# Setting task status
|
||||
try:
|
||||
task["status"] = new_status
|
||||
task_entity["status"] = new_status
|
||||
session.commit()
|
||||
self.log.debug("[ {} ] Status updated to [ {} ]".format(
|
||||
ent_path, new_status['name']
|
||||
ent_path, new_status["name"]
|
||||
))
|
||||
except Exception:
|
||||
session.rollback()
|
||||
|
|
@ -128,6 +230,22 @@ class VersionToTaskStatus(BaseEvent):
|
|||
exc_info=True
|
||||
)
|
||||
|
||||
def statuses_for_tasks(self, session, task_entities, project_entity):
|
||||
task_type_ids = set()
|
||||
for task_entity in task_entities:
|
||||
task_type_ids.add(task_entity["type_id"])
|
||||
|
||||
project_schema = project_entity["project_schema"]
|
||||
output = {}
|
||||
for task_type_id in task_type_ids:
|
||||
statuses = project_schema.get_statuses("Task", task_type_id)
|
||||
output[task_type_id] = {
|
||||
status["name"].lower(): status
|
||||
for status in statuses
|
||||
}
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def register(session, plugins_presets):
|
||||
'''Register plugin. Called when used as an plugin.'''
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ from pype.modules.ftrack.ftrack_server.lib import (
|
|||
get_ftrack_event_mongo_info
|
||||
)
|
||||
|
||||
import socket_thread
|
||||
from pype.modules.ftrack.ftrack_server import socket_thread
|
||||
|
||||
|
||||
class MongoPermissionsError(Exception):
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ import inspect
|
|||
|
||||
import ftrack_api
|
||||
|
||||
from pype.api import Logger
|
||||
from pype.lib import PypeLogger
|
||||
|
||||
|
||||
log = Logger().get_logger(__name__)
|
||||
log = PypeLogger().get_logger(__name__)
|
||||
|
||||
"""
|
||||
# Required - Needed for connection to Ftrack
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import functools
|
||||
import time
|
||||
from pype.api import Logger
|
||||
from pype.settings import get_project_settings
|
||||
|
||||
import ftrack_api
|
||||
from pype.modules.ftrack import ftrack_server
|
||||
|
||||
|
|
@ -581,3 +583,67 @@ class BaseHandler(object):
|
|||
return self.session.query(
|
||||
"Project where id is {}".format(project_data["id"])
|
||||
).one()
|
||||
|
||||
def get_project_entity_from_event(self, session, event, project_id):
|
||||
"""Load or query and fill project entity from/to event data.
|
||||
|
||||
Project data are stored by ftrack id because in most cases it is
|
||||
easier to access project id than project name.
|
||||
|
||||
Args:
|
||||
session (ftrack_api.Session): Current session.
|
||||
event (ftrack_api.Event): Processed event by session.
|
||||
project_id (str): Ftrack project id.
|
||||
"""
|
||||
if not project_id:
|
||||
raise ValueError(
|
||||
"Entered `project_id` is not valid. {} ({})".format(
|
||||
str(project_id), str(type(project_id))
|
||||
)
|
||||
)
|
||||
# Try to get project entity from event
|
||||
project_entities = event["data"].get("project_entities")
|
||||
if not project_entities:
|
||||
project_entities = {}
|
||||
event["data"]["project_entities"] = project_entities
|
||||
|
||||
project_entity = project_entities.get(project_id)
|
||||
if not project_entity:
|
||||
# Get project entity from task and store to event
|
||||
project_entity = session.get("Project", project_id)
|
||||
event["data"]["project_entities"][project_id] = project_entity
|
||||
return project_entity
|
||||
|
||||
def get_settings_for_project(
|
||||
self, session, event, project_id=None, project_entity=None
|
||||
):
|
||||
"""Load or fill pype's project settings from event data.
|
||||
|
||||
Project data are stored by ftrack id because in most cases it is
|
||||
easier to access project id than project name.
|
||||
|
||||
Args:
|
||||
session (ftrack_api.Session): Current session.
|
||||
event (ftrack_api.Event): Processed event by session.
|
||||
project_id (str): Ftrack project id. Must be entered if
|
||||
project_entity is not.
|
||||
project_entity (ftrack_api.Entity): Project entity. Must be entered
|
||||
if project_id is not.
|
||||
"""
|
||||
if not project_entity:
|
||||
project_entity = self.get_project_entity_from_event(
|
||||
session, event, project_id
|
||||
)
|
||||
|
||||
project_name = project_entity["full_name"]
|
||||
|
||||
project_settings_by_id = event["data"].get("project_settings")
|
||||
if not project_settings_by_id:
|
||||
project_settings_by_id = {}
|
||||
event["data"]["project_settings"] = project_settings_by_id
|
||||
|
||||
project_settings = project_settings_by_id.get(project_id)
|
||||
if not project_settings:
|
||||
project_settings = get_project_settings(project_name)
|
||||
event["data"]["project_settings"][project_id] = project_settings
|
||||
return project_settings
|
||||
|
|
|
|||
|
|
@ -60,7 +60,18 @@ class PypeCommands:
|
|||
return return_code
|
||||
|
||||
def launch_eventservercli(self, args):
|
||||
pass
|
||||
from pype.modules import ftrack
|
||||
from pype.lib import execute
|
||||
|
||||
fname = os.path.join(
|
||||
os.path.dirname(os.path.abspath(ftrack.__file__)),
|
||||
"ftrack_server",
|
||||
"event_server_cli.py"
|
||||
)
|
||||
|
||||
return execute([
|
||||
sys.executable, "-u", fname
|
||||
])
|
||||
|
||||
def publish(self, gui, paths):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -71,11 +71,13 @@
|
|||
"status_version_to_task": {
|
||||
"enabled": true,
|
||||
"mapping": {
|
||||
"Complete": [
|
||||
"Approved",
|
||||
"Approved": [
|
||||
"Complete"
|
||||
]
|
||||
}
|
||||
},
|
||||
"asset_types_to_skip": [
|
||||
"scene"
|
||||
]
|
||||
},
|
||||
"first_version_status": {
|
||||
"enabled": true,
|
||||
|
|
|
|||
|
|
@ -179,4 +179,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -136,7 +136,8 @@
|
|||
"enabled": false
|
||||
},
|
||||
"ValidateAttributes": {
|
||||
"enabled": false
|
||||
"enabled": false,
|
||||
"attributes": {}
|
||||
},
|
||||
"ExtractCameraAlembic": {
|
||||
"enabled": true,
|
||||
|
|
|
|||
|
|
@ -87,4 +87,4 @@
|
|||
]
|
||||
},
|
||||
"filters": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -204,20 +204,38 @@
|
|||
"label": "Sync status from Version to Task",
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "dict-modifiable",
|
||||
"key": "mapping",
|
||||
"object_type":
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"label": "<b>Change Task status based on a changed Version status.</b><br/>Version's new status on the <b>left</b> will trigger a change of a task status to the first available from the list on <b>right</b>.<br/> - if no status from the list is available it will use the same status as the version."
|
||||
},
|
||||
{
|
||||
"type": "dict-modifiable",
|
||||
"key": "mapping",
|
||||
"object_type":
|
||||
{
|
||||
"type": "list",
|
||||
"object_type": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"label": "<b>Disable<b/> event if status was changed on specific Asset type."
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"label": "Asset types (short)",
|
||||
"key": "asset_types_to_skip",
|
||||
"object_type": "text"
|
||||
}
|
||||
}]
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue