From 76beb8bba5c4a8bdda60110fe086d527ce9a7bf1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 15 Jul 2019 12:02:30 +0200 Subject: [PATCH 1/9] renamed users action sync hierarchical attributes to sync hierarchical attributes local --- ...ion_sync_hier_attrs.py => action_sync_hier_attrs_local.py} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename pype/ftrack/actions/{action_sync_hier_attrs.py => action_sync_hier_attrs_local.py} (98%) diff --git a/pype/ftrack/actions/action_sync_hier_attrs.py b/pype/ftrack/actions/action_sync_hier_attrs_local.py similarity index 98% rename from pype/ftrack/actions/action_sync_hier_attrs.py rename to pype/ftrack/actions/action_sync_hier_attrs_local.py index 3a884a017f..29c9f33c44 100644 --- a/pype/ftrack/actions/action_sync_hier_attrs.py +++ b/pype/ftrack/actions/action_sync_hier_attrs_local.py @@ -16,9 +16,9 @@ class SyncHierarchicalAttrs(BaseAction): ca_mongoid = lib.get_ca_mongoid() #: Action identifier. - identifier = 'sync.hierarchical.attrs' + identifier = 'sync.hierarchical.attrs.local' #: Action label. - label = 'Sync hierarchical attributes' + label = 'Sync hierarchical attributes - Local' #: Action description. description = 'Synchronize hierarchical attributes' #: Icon From 4fa113fba0d467347b0e3b8f2c13d1fbf2cdbfee Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 15 Jul 2019 12:03:29 +0200 Subject: [PATCH 2/9] added job to sync hierarchical attrs --- .../actions/action_sync_hier_attrs_local.py | 131 +++++++++++------- 1 file changed, 78 insertions(+), 53 deletions(-) diff --git a/pype/ftrack/actions/action_sync_hier_attrs_local.py b/pype/ftrack/actions/action_sync_hier_attrs_local.py index 29c9f33c44..52c64841c2 100644 --- a/pype/ftrack/actions/action_sync_hier_attrs_local.py +++ b/pype/ftrack/actions/action_sync_hier_attrs_local.py @@ -40,67 +40,92 @@ class SyncHierarchicalAttrs(BaseAction): return False def launch(self, session, entities, event): - # Collect hierarchical attrs - custom_attributes = {} - all_avalon_attr = session.query( - 'CustomAttributeGroup where name is "avalon"' + user = session.query( + 'User where id is "{}"'.format(event['source']['user']['id']) ).one() - for cust_attr in all_avalon_attr['custom_attribute_configurations']: - if 'avalon_' in cust_attr['key']: - continue - if not cust_attr['is_hierarchical']: - continue + job = session.create('Job', { + 'user': user, + 'status': 'running', + 'data': json.dumps({ + 'description': 'Sync Hierachical attributes' + }) + }) + session.commit() - if cust_attr['default']: - self.log.warning(( - 'Custom attribute "{}" has set default value.' - ' This attribute can\'t be synchronized' - ).format(cust_attr['label'])) - continue - - custom_attributes[cust_attr['key']] = cust_attr - - if not custom_attributes: - msg = 'No hierarchical attributes to sync.' - self.log.debug(msg) - return { - 'success': True, - 'message': msg - } - - entity = entities[0] - if entity.entity_type.lower() == 'project': - project_name = entity['full_name'] - else: - project_name = entity['project']['full_name'] - - self.db_con.install() - self.db_con.Session['AVALON_PROJECT'] = project_name - - for entity in entities: - for key in custom_attributes: - # check if entity has that attribute - if key not in entity['custom_attributes']: - self.log.debug( - 'Hierachical attribute "{}" not found on "{}"'.format( - key, entity.get('name', entity) - ) - ) + try: + # Collect hierarchical attrs + custom_attributes = {} + all_avalon_attr = session.query( + 'CustomAttributeGroup where name is "avalon"' + ).one() + for cust_attr in all_avalon_attr['custom_attribute_configurations']: + if 'avalon_' in cust_attr['key']: continue - value = self.get_hierarchical_value(key, entity) - if value is None: - self.log.warning( - 'Hierarchical attribute "{}" not set on "{}"'.format( - key, entity.get('name', entity) - ) - ) + if not cust_attr['is_hierarchical']: continue - self.update_hierarchical_attribute(entity, key, value) + if cust_attr['default']: + self.log.warning(( + 'Custom attribute "{}" has set default value.' + ' This attribute can\'t be synchronized' + ).format(cust_attr['label'])) + continue - self.db_con.uninstall() + custom_attributes[cust_attr['key']] = cust_attr + + if not custom_attributes: + msg = 'No hierarchical attributes to sync.' + self.log.debug(msg) + return { + 'success': True, + 'message': msg + } + + entity = entities[0] + if entity.entity_type.lower() == 'project': + project_name = entity['full_name'] + else: + project_name = entity['project']['full_name'] + + self.db_con.install() + self.db_con.Session['AVALON_PROJECT'] = project_name + + for entity in entities: + for key in custom_attributes: + # check if entity has that attribute + if key not in entity['custom_attributes']: + self.log.debug( + 'Hierachical attribute "{}" not found on "{}"'.format( + key, entity.get('name', entity) + ) + ) + continue + + value = self.get_hierarchical_value(key, entity) + if value is None: + self.log.warning( + 'Hierarchical attribute "{}" not set on "{}"'.format( + key, entity.get('name', entity) + ) + ) + continue + + self.update_hierarchical_attribute(entity, key, value) + + except Exception: + self.log.error( + 'Action "{}" failed'.format(self.label), + exc_info=True + ) + + finally: + self.db_con.uninstall() + + if job['status'] in ('queued', 'running'): + job['status'] = 'failed' + session.commit() return True From b06f3a60a2d9dce414f8fb1be171462f13336d41 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 15 Jul 2019 12:03:59 +0200 Subject: [PATCH 3/9] sync hier attrs action is launched after sync to avalon --- pype/ftrack/actions/action_sync_to_avalon_local.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pype/ftrack/actions/action_sync_to_avalon_local.py b/pype/ftrack/actions/action_sync_to_avalon_local.py index bed28e1bef..34070c7e1f 100644 --- a/pype/ftrack/actions/action_sync_to_avalon_local.py +++ b/pype/ftrack/actions/action_sync_to_avalon_local.py @@ -6,6 +6,7 @@ import json from pype.vendor import ftrack_api from pype.ftrack import BaseAction, lib as ftracklib +from pype.vendor.ftrack_api import session as fa_session class SyncToAvalon(BaseAction): @@ -176,6 +177,18 @@ class SyncToAvalon(BaseAction): job['status'] = 'failed' session.commit() + event = fa_session.ftrack_api.event.base.Event( + topic='ftrack.action.launch', + data=dict( + actionIdentifier='sync.hierarchical.attrs.local', + selection=event['data']['selection'] + ), + source=dict( + user=event['source']['user'] + ) + ) + session.event_hub.publish(event, on_error='ignore') + if len(message) > 0: message = "Unable to sync: {}".format(message) return { From 4728d6065c2a2c05b0dd83b874b6adc7945793cf Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 15 Jul 2019 12:42:20 +0200 Subject: [PATCH 4/9] added json import --- pype/ftrack/actions/action_sync_hier_attrs_local.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/ftrack/actions/action_sync_hier_attrs_local.py b/pype/ftrack/actions/action_sync_hier_attrs_local.py index 52c64841c2..bb8e7c5282 100644 --- a/pype/ftrack/actions/action_sync_hier_attrs_local.py +++ b/pype/ftrack/actions/action_sync_hier_attrs_local.py @@ -1,5 +1,6 @@ import os import sys +import json import argparse import logging import collections From b7eaad01feda384e938efab95e24046d48dccb93 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 15 Jul 2019 12:42:37 +0200 Subject: [PATCH 5/9] shortened action label --- pype/ftrack/actions/action_sync_hier_attrs_local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/ftrack/actions/action_sync_hier_attrs_local.py b/pype/ftrack/actions/action_sync_hier_attrs_local.py index bb8e7c5282..21eea19436 100644 --- a/pype/ftrack/actions/action_sync_hier_attrs_local.py +++ b/pype/ftrack/actions/action_sync_hier_attrs_local.py @@ -19,7 +19,7 @@ class SyncHierarchicalAttrs(BaseAction): #: Action identifier. identifier = 'sync.hierarchical.attrs.local' #: Action label. - label = 'Sync hierarchical attributes - Local' + label = 'Sync HierAttrs - Local' #: Action description. description = 'Synchronize hierarchical attributes' #: Icon From 7356599adb5115f671d76cb704445194f607b332 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 15 Jul 2019 12:42:56 +0200 Subject: [PATCH 6/9] added local action icon --- pype/ftrack/actions/action_sync_hier_attrs_local.py | 2 +- res/ftrack/action_icons/SyncHierarchicalAttrsLocal.svg | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 res/ftrack/action_icons/SyncHierarchicalAttrsLocal.svg diff --git a/pype/ftrack/actions/action_sync_hier_attrs_local.py b/pype/ftrack/actions/action_sync_hier_attrs_local.py index 21eea19436..c6b12028bc 100644 --- a/pype/ftrack/actions/action_sync_hier_attrs_local.py +++ b/pype/ftrack/actions/action_sync_hier_attrs_local.py @@ -23,7 +23,7 @@ class SyncHierarchicalAttrs(BaseAction): #: Action description. description = 'Synchronize hierarchical attributes' #: Icon - icon = '{}/ftrack/action_icons/SyncHierarchicalAttrs.svg'.format( + icon = '{}/ftrack/action_icons/SyncHierarchicalAttrsLocal.svg'.format( os.environ.get('PYPE_STATICS_SERVER', '') ) diff --git a/res/ftrack/action_icons/SyncHierarchicalAttrsLocal.svg b/res/ftrack/action_icons/SyncHierarchicalAttrsLocal.svg new file mode 100644 index 0000000000..f58448ac06 --- /dev/null +++ b/res/ftrack/action_icons/SyncHierarchicalAttrsLocal.svg @@ -0,0 +1 @@ + From 2d0fdfbbc99ccb63c3327ad28adedc1f2c675a9c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 15 Jul 2019 12:43:44 +0200 Subject: [PATCH 7/9] added sync hierarchical attributes action for event server --- pype/ftrack/events/action_sync_hier_attrs.py | 274 ++++++++++++++++++ .../action_icons/SyncHierarchicalAttrs.svg | 10 +- 2 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 pype/ftrack/events/action_sync_hier_attrs.py diff --git a/pype/ftrack/events/action_sync_hier_attrs.py b/pype/ftrack/events/action_sync_hier_attrs.py new file mode 100644 index 0000000000..7fa024edf4 --- /dev/null +++ b/pype/ftrack/events/action_sync_hier_attrs.py @@ -0,0 +1,274 @@ +import os +import sys +import json +import argparse +import logging +import collections + +from pypeapp import config +from pype.vendor import ftrack_api +from pype.ftrack import BaseAction, lib +from avalon.tools.libraryloader.io_nonsingleton import DbConnector +from bson.objectid import ObjectId + + +class SyncHierarchicalAttrs(BaseAction): + + db_con = DbConnector() + ca_mongoid = lib.get_ca_mongoid() + + #: Action identifier. + identifier = 'sync.hierarchical.attrs' + #: Action label. + label = 'Sync HierAttrs' + #: Action description. + description = 'Synchronize hierarchical attributes' + #: Icon + icon = '{}/ftrack/action_icons/SyncHierarchicalAttrs.svg'.format( + os.environ.get( + 'PYPE_STATICS_SERVER', + 'http://localhost:{}'.format( + config.get_presets().get('services', {}).get( + 'statics_server', {} + ).get('default_port', 8021) + ) + ) + ) + + def register(self): + self.session.event_hub.subscribe( + 'topic=ftrack.action.discover', + self._discover + ) + + self.session.event_hub.subscribe( + 'topic=ftrack.action.launch and data.actionIdentifier={}'.format( + self.identifier + ), + self._launch + ) + + def discover(self, session, entities, event): + ''' Validation ''' + role_check = False + discover = False + role_list = ['Pypeclub', 'Administrator', 'Project Manager'] + user = session.query( + 'User where id is "{}"'.format(event['source']['user']['id']) + ).one() + + for role in user['user_security_roles']: + if role['security_role']['name'] in role_list: + role_check = True + break + print(self.icon) + if role_check is True: + for entity in entities: + context_type = entity.get('context_type', '').lower() + if ( + context_type in ('show', 'task') and + entity.entity_type.lower() != 'task' + ): + discover = True + break + + return discover + + def launch(self, session, entities, event): + user = session.query( + 'User where id is "{}"'.format(event['source']['user']['id']) + ).one() + + job = session.create('Job', { + 'user': user, + 'status': 'running', + 'data': json.dumps({ + 'description': 'Sync Hierachical attributes' + }) + }) + session.commit() + + try: + # Collect hierarchical attrs + custom_attributes = {} + all_avalon_attr = session.query( + 'CustomAttributeGroup where name is "avalon"' + ).one() + for cust_attr in all_avalon_attr['custom_attribute_configurations']: + if 'avalon_' in cust_attr['key']: + continue + + if not cust_attr['is_hierarchical']: + continue + + if cust_attr['default']: + self.log.warning(( + 'Custom attribute "{}" has set default value.' + ' This attribute can\'t be synchronized' + ).format(cust_attr['label'])) + continue + + custom_attributes[cust_attr['key']] = cust_attr + + if not custom_attributes: + msg = 'No hierarchical attributes to sync.' + self.log.debug(msg) + return { + 'success': True, + 'message': msg + } + + entity = entities[0] + if entity.entity_type.lower() == 'project': + project_name = entity['full_name'] + else: + project_name = entity['project']['full_name'] + + self.db_con.install() + self.db_con.Session['AVALON_PROJECT'] = project_name + + for entity in entities: + for key in custom_attributes: + # check if entity has that attribute + if key not in entity['custom_attributes']: + self.log.debug( + 'Hierachical attribute "{}" not found on "{}"'.format( + key, entity.get('name', entity) + ) + ) + continue + + value = self.get_hierarchical_value(key, entity) + if value is None: + self.log.warning( + 'Hierarchical attribute "{}" not set on "{}"'.format( + key, entity.get('name', entity) + ) + ) + continue + + self.update_hierarchical_attribute(entity, key, value) + + except Exception: + self.log.error( + 'Action "{}" failed'.format(self.label), + exc_info=True + ) + + finally: + self.db_con.uninstall() + + if job['status'] in ('queued', 'running'): + job['status'] = 'failed' + session.commit() + + return True + + def get_hierarchical_value(self, key, entity): + value = entity['custom_attributes'][key] + if ( + value is not None or + entity.entity_type.lower() == 'project' + ): + return value + + return self.get_hierarchical_value(key, entity['parent']) + + def update_hierarchical_attribute(self, entity, key, value): + if ( + entity['context_type'].lower() not in ('show', 'task') or + entity.entity_type.lower() == 'task' + ): + return + # collect entity's custom attributes + custom_attributes = entity.get('custom_attributes') + if not custom_attributes: + return + + mongoid = custom_attributes.get(self.ca_mongoid) + if not mongoid: + self.log.debug('Entity "{}" is not synchronized to avalon.'.format( + entity.get('name', entity) + )) + return + + try: + mongoid = ObjectId(mongoid) + except Exception: + self.log.warning('Entity "{}" has stored invalid MongoID.'.format( + entity.get('name', entity) + )) + return + # Find entity in Mongo DB + mongo_entity = self.db_con.find_one({'_id': mongoid}) + if not mongo_entity: + self.log.warning( + 'Entity "{}" is not synchronized to avalon.'.format( + entity.get('name', entity) + ) + ) + return + + # Change value if entity has set it's own + entity_value = custom_attributes[key] + if entity_value is not None: + value = entity_value + + data = mongo_entity.get('data') or {} + + data[key] = value + self.db_con.update_many( + {'_id': mongoid}, + {'$set': {'data': data}} + ) + + for child in entity.get('children', []): + self.update_hierarchical_attribute(child, key, value) + + +def register(session, **kw): + '''Register plugin. Called when used as an plugin.''' + + if not isinstance(session, ftrack_api.session.Session): + return + + SyncHierarchicalAttrs(session).register() + + +def main(arguments=None): + '''Set up logging and register action.''' + if arguments is None: + arguments = [] + + parser = argparse.ArgumentParser() + # Allow setting of logging level from arguments. + loggingLevels = {} + for level in ( + logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING, + logging.ERROR, logging.CRITICAL + ): + loggingLevels[logging.getLevelName(level).lower()] = level + + parser.add_argument( + '-v', '--verbosity', + help='Set the logging output verbosity.', + choices=loggingLevels.keys(), + default='info' + ) + namespace = parser.parse_args(arguments) + + # Set up basic logging + logging.basicConfig(level=loggingLevels[namespace.verbosity]) + + session = ftrack_api.Session() + register(session) + + # Wait for events + logging.info( + 'Registered actions and listening for events. Use Ctrl-C to abort.' + ) + session.event_hub.wait() + + +if __name__ == '__main__': + raise SystemExit(main(sys.argv[1:])) diff --git a/res/ftrack/action_icons/SyncHierarchicalAttrs.svg b/res/ftrack/action_icons/SyncHierarchicalAttrs.svg index 0c59189168..8b7953299f 100644 --- a/res/ftrack/action_icons/SyncHierarchicalAttrs.svg +++ b/res/ftrack/action_icons/SyncHierarchicalAttrs.svg @@ -1 +1,9 @@ - \ No newline at end of file + + + + + + + + + From 3e894ec7b014ee6de4d1cfa24ddef72b3ef50359 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 15 Jul 2019 12:45:40 +0200 Subject: [PATCH 8/9] sync to avalon action launches sync shierarcdhical attrs when finishes --- pype/ftrack/events/action_sync_to_avalon.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/pype/ftrack/events/action_sync_to_avalon.py b/pype/ftrack/events/action_sync_to_avalon.py index f1a5b37f36..46a25caea5 100644 --- a/pype/ftrack/events/action_sync_to_avalon.py +++ b/pype/ftrack/events/action_sync_to_avalon.py @@ -5,6 +5,7 @@ import logging import json from pype.vendor import ftrack_api from pype.ftrack import BaseAction, lib +from pype.vendor.ftrack_api import session as fa_session class Sync_To_Avalon(BaseAction): @@ -70,7 +71,7 @@ class Sync_To_Avalon(BaseAction): ''' Validation ''' roleCheck = False discover = False - roleList = ['Administrator', 'Project Manager'] + roleList = ['Pypeclub', 'Administrator', 'Project Manager'] userId = event['source']['user']['id'] user = session.query('User where id is ' + userId).one() @@ -191,6 +192,24 @@ class Sync_To_Avalon(BaseAction): ' - Please check Log for more information' ) + finally: + if job['status'] in ['queued', 'running']: + job['status'] = 'failed' + + session.commit() + + event = fa_session.ftrack_api.event.base.Event( + topic='ftrack.action.launch', + data=dict( + actionIdentifier='sync.hierarchical.attrs', + selection=event['data']['selection'] + ), + source=dict( + user=event['source']['user'] + ) + ) + session.event_hub.publish(event, on_error='ignore') + if len(message) > 0: message = "Unable to sync: {}".format(message) return { From 1b3f72936ec97df3c6f990ec00eb2aca1d0a66a5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 15 Jul 2019 12:45:58 +0200 Subject: [PATCH 9/9] icon "fix" --- pype/ftrack/events/action_sync_to_avalon.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pype/ftrack/events/action_sync_to_avalon.py b/pype/ftrack/events/action_sync_to_avalon.py index 46a25caea5..e78b209fac 100644 --- a/pype/ftrack/events/action_sync_to_avalon.py +++ b/pype/ftrack/events/action_sync_to_avalon.py @@ -3,6 +3,8 @@ import sys import argparse import logging import json + +from pypeapp import config from pype.vendor import ftrack_api from pype.ftrack import BaseAction, lib from pype.vendor.ftrack_api import session as fa_session @@ -51,7 +53,14 @@ class Sync_To_Avalon(BaseAction): description = 'Send data from Ftrack to Avalon' #: Action icon. icon = '{}/ftrack/action_icons/SyncToAvalon.svg'.format( - os.environ.get('PYPE_STATICS_SERVER', '') + os.environ.get( + 'PYPE_STATICS_SERVER', + 'http://localhost:{}'.format( + config.get_presets().get('services', {}).get( + 'statics_server', {} + ).get('default_port', 8021) + ) + ) ) def register(self):