From d7883fe561238073e59fde455b2d7845ec7ad7aa Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 20 Jul 2020 11:21:47 +0100 Subject: [PATCH 01/58] Use published workfile for rendering. --- .../nuke/publish/submit_nuke_deadline.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pype/plugins/nuke/publish/submit_nuke_deadline.py b/pype/plugins/nuke/publish/submit_nuke_deadline.py index 2b8efb4640..26d3f9b571 100644 --- a/pype/plugins/nuke/publish/submit_nuke_deadline.py +++ b/pype/plugins/nuke/publish/submit_nuke_deadline.py @@ -49,6 +49,24 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): render_path = instance.data['path'] script_path = context.data["currentFile"] + for item in context: + if "workfile" in item.data["families"]: + msg = "Workfile (scene) must be published along" + assert item.data["publish"] is True, msg + + template_data = item.data.get("anatomyData") + rep = item.data.get("representations")[0].get("name") + template_data["representation"] = rep + template_data["ext"] = rep + template_data["comment"] = None + anatomy_filled = context.data["anatomy"].format(template_data) + template_filled = anatomy_filled["publish"]["path"] + script_path = os.path.normpath(template_filled) + + self.log.info( + "Using published scene for render {}".format(script_path) + ) + # exception for slate workflow if "slate" in instance.data["families"]: self._frame_start -= 1 From fa91da78ee6956d04e2f3d1ca412d8ef933f622b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 20 Jul 2020 13:00:29 +0200 Subject: [PATCH 02/58] optional plugins with active attribute set to False are skipped --- pype/tools/pyblish_pype/control.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pype/tools/pyblish_pype/control.py b/pype/tools/pyblish_pype/control.py index 5138b5cc4c..77badf71b6 100644 --- a/pype/tools/pyblish_pype/control.py +++ b/pype/tools/pyblish_pype/control.py @@ -183,7 +183,18 @@ class Controller(QtCore.QObject): plugins = pyblish.api.discover() targets = pyblish.logic.registered_targets() or ["default"] - self.plugins = pyblish.logic.plugins_by_targets(plugins, targets) + plugins_by_targets = pyblish.logic.plugins_by_targets(plugins, targets) + + _plugins = [] + for plugin in plugins_by_targets: + # Skip plugin if is not optional and not active + if ( + not getattr(plugin, "optional", False) + and not getattr(plugin, "active", True) + ): + continue + _plugins.append(plugin) + self.plugins = _plugins def on_published(self): if self.is_running: From 059082a671cf414c028d64cc190b9a03272ae7e3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Jul 2020 11:55:27 +0200 Subject: [PATCH 03/58] fix(ppro): audio only was not working --- pype/hosts/premiere/extensions/com.pype/jsx/pype.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/hosts/premiere/extensions/com.pype/jsx/pype.jsx b/pype/hosts/premiere/extensions/com.pype/jsx/pype.jsx index 684cef5e5a..3cd4502653 100644 --- a/pype/hosts/premiere/extensions/com.pype/jsx/pype.jsx +++ b/pype/hosts/premiere/extensions/com.pype/jsx/pype.jsx @@ -534,7 +534,9 @@ $.pype = { if (instances === null) { return null; } - if (audioOnly === true) { + + // make only audio representations + if (audioOnly === 'true') { $.pype.log('? looping if audio True'); for (var i = 0; i < instances.length; i++) { var subsetToRepresentations = instances[i].subsetToRepresentations; From 19a33b7272843c06a592a3fe044dc82fb806ff9e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Jul 2020 12:08:23 +0200 Subject: [PATCH 04/58] feat(ppro): synchronization of ftrack project back on --- .../publish/integrate_auto_sync_back_on.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 pype/plugins/premiere/publish/integrate_auto_sync_back_on.py diff --git a/pype/plugins/premiere/publish/integrate_auto_sync_back_on.py b/pype/plugins/premiere/publish/integrate_auto_sync_back_on.py new file mode 100644 index 0000000000..ca7151d3d3 --- /dev/null +++ b/pype/plugins/premiere/publish/integrate_auto_sync_back_on.py @@ -0,0 +1,35 @@ +import sys +import pyblish.api +import avalon.api + +try: + from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_AUTO_SYNC +except Exception: + CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" + + +class IntegrateAutoSyncBackOn(pyblish.api.ContextPlugin): + """Ensure that autosync value in ftrack project is set to True. + + In case was set to False and event server with the sync to avalon event + is not running this will set synchronization back on. + """ + + order = pyblish.api.IntegratorOrder + 1 + families = ['clip'] + label = 'Ftrack project\'s auto sync on' + + def process(self, context): + session = context.data["ftrackSession"] + project_name = avalon.api.Session["AVALON_PROJECT"] + query = 'Project where full_name is "{}"'.format(project_name) + project = session.query(query).one() + if not project["custom_attributes"][CUST_ATTR_AUTO_SYNC]: + project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = True + + try: + session.commit() + except Exception: + tp, value, tb = sys.exc_info() + session.rollback() + raise From 60a9b6bface827b1190d8108f55398b59eab16d2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Jul 2020 14:27:55 +0200 Subject: [PATCH 05/58] feat(ftrack): auto-sync switcher moved to frack hierarchy integrator --- .../publish/integrate_hierarchy_ftrack.py | 49 ++++++++++++++++- .../publish/integrate_auto_sync_back_on.py | 35 ------------ .../publish/validate_auto_sync_off.py | 55 ------------------- 3 files changed, 47 insertions(+), 92 deletions(-) delete mode 100644 pype/plugins/premiere/publish/integrate_auto_sync_back_on.py delete mode 100644 pype/plugins/premiere/publish/validate_auto_sync_off.py diff --git a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py index a12fdfd36c..7a43daf781 100644 --- a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py +++ b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py @@ -1,9 +1,13 @@ import sys - import six import pyblish.api from avalon import io +try: + from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_AUTO_SYNC +except Exception: + CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" + class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): """ @@ -47,7 +51,16 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): input_data = context.data["hierarchyContext"] - self.import_to_ftrack(input_data) + # disable termporarily ftrack project's autosyncing + self.auto_sync_off(context) + + try: + # import ftrack hierarchy + self.import_to_ftrack(input_data) + except Exception: + raise + finally: + self.auto_sync_on() def import_to_ftrack(self, input_data, parent=None): for entity_name in input_data: @@ -217,3 +230,35 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): six.reraise(tp, value, tb) return entity + + def auto_sync_off(self, context): + project_name = context.data["projectEntity"]["name"] + query = 'Project where full_name is "{}"'.format(project_name) + self.project = self.session.query(query).one() + self.auto_sync_state = self.project[ + "custom_attributes"][CUST_ATTR_AUTO_SYNC] + + self.project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = False + + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + raise + + self.log.info("Ftrack autosync swithed off") + + def auto_sync_on(self): + if not self.project[ + "custom_attributes"][CUST_ATTR_AUTO_SYNC] \ + and self.auto_sync_state: + self.project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = True + self.log.info("Ftrack autosync swithed on") + + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + raise diff --git a/pype/plugins/premiere/publish/integrate_auto_sync_back_on.py b/pype/plugins/premiere/publish/integrate_auto_sync_back_on.py deleted file mode 100644 index ca7151d3d3..0000000000 --- a/pype/plugins/premiere/publish/integrate_auto_sync_back_on.py +++ /dev/null @@ -1,35 +0,0 @@ -import sys -import pyblish.api -import avalon.api - -try: - from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_AUTO_SYNC -except Exception: - CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" - - -class IntegrateAutoSyncBackOn(pyblish.api.ContextPlugin): - """Ensure that autosync value in ftrack project is set to True. - - In case was set to False and event server with the sync to avalon event - is not running this will set synchronization back on. - """ - - order = pyblish.api.IntegratorOrder + 1 - families = ['clip'] - label = 'Ftrack project\'s auto sync on' - - def process(self, context): - session = context.data["ftrackSession"] - project_name = avalon.api.Session["AVALON_PROJECT"] - query = 'Project where full_name is "{}"'.format(project_name) - project = session.query(query).one() - if not project["custom_attributes"][CUST_ATTR_AUTO_SYNC]: - project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = True - - try: - session.commit() - except Exception: - tp, value, tb = sys.exc_info() - session.rollback() - raise diff --git a/pype/plugins/premiere/publish/validate_auto_sync_off.py b/pype/plugins/premiere/publish/validate_auto_sync_off.py deleted file mode 100644 index cd6fef29c8..0000000000 --- a/pype/plugins/premiere/publish/validate_auto_sync_off.py +++ /dev/null @@ -1,55 +0,0 @@ -import sys -import pyblish.api -import pype.api -import avalon.api - -try: - from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_AUTO_SYNC -except Exception: - CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" - - -class ValidateAutoSyncOff(pyblish.api.ContextPlugin): - """Ensure that autosync value in ftrack project is set to False. - - In case was set to True and event server with the sync to avalon event - is running will cause integration to avalon will be override. - - """ - - order = pyblish.api.ValidatorOrder - families = ['clip'] - label = 'Ftrack project\'s auto sync off' - actions = [pype.api.RepairContextAction] - - def process(self, context): - invalid = self.get_invalid(context) - assert not invalid, ( - "Ftrack Project has 'Auto sync' set to On." - " That may cause issues during integration." - ) - - @staticmethod - def get_invalid(context): - session = context.data["ftrackSession"] - project_name = avalon.api.Session["AVALON_PROJECT"] - query = 'Project where full_name is "{}"'.format(project_name) - project = session.query(query).one() - if project["custom_attributes"][CUST_ATTR_AUTO_SYNC]: - return project - - @classmethod - def repair(cls, context): - session = context.data["ftrackSession"] - invalid = cls.get_invalid(context) - if not invalid: - cls.log.info("Project 'Auto sync' already fixed.") - return - - invalid["custom_attributes"][CUST_ATTR_AUTO_SYNC] = False - try: - session.commit() - except Exception: - tp, value, tb = sys.exc_info() - session.rollback() - raise From 45e2de4d08227ebd1634f1cde8bdf2d7bb79dbde Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Jul 2020 14:35:48 +0200 Subject: [PATCH 06/58] fix(ftrack): refactoring code --- .../publish/integrate_hierarchy_ftrack.py | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py index 7a43daf781..05908cb2da 100644 --- a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py +++ b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py @@ -43,6 +43,12 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): if "hierarchyContext" not in context.data: return + project_name = context.data["projectEntity"]["name"] + query = 'Project where full_name is "{}"'.format(project_name) + project = self.session.query(query).one() + auto_sync_state = project[ + "custom_attributes"][CUST_ATTR_AUTO_SYNC] + if not io.Session: io.install() @@ -52,7 +58,8 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): input_data = context.data["hierarchyContext"] # disable termporarily ftrack project's autosyncing - self.auto_sync_off(context) + if auto_sync_state: + self.auto_sync_off(project) try: # import ftrack hierarchy @@ -60,7 +67,8 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): except Exception: raise finally: - self.auto_sync_on() + if auto_sync_state: + self.auto_sync_on(project) def import_to_ftrack(self, input_data, parent=None): for entity_name in input_data: @@ -231,30 +239,23 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): return entity - def auto_sync_off(self, context): - project_name = context.data["projectEntity"]["name"] - query = 'Project where full_name is "{}"'.format(project_name) - self.project = self.session.query(query).one() - self.auto_sync_state = self.project[ - "custom_attributes"][CUST_ATTR_AUTO_SYNC] - - self.project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = False - - try: - self.session.commit() - except Exception: - tp, value, tb = sys.exc_info() - self.session.rollback() - raise + def auto_sync_off(self, project): + project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = False self.log.info("Ftrack autosync swithed off") - def auto_sync_on(self): - if not self.project[ - "custom_attributes"][CUST_ATTR_AUTO_SYNC] \ - and self.auto_sync_state: - self.project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = True - self.log.info("Ftrack autosync swithed on") + try: + self.session.commit() + except Exception: + tp, value, tb = sys.exc_info() + self.session.rollback() + raise + + def auto_sync_on(self, project): + + project["custom_attributes"][CUST_ATTR_AUTO_SYNC] = True + + self.log.info("Ftrack autosync swithed on") try: self.session.commit() From eb28ba7a5f90f746b7835fdec97b2d957cf14089 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 21 Jul 2020 14:40:56 +0200 Subject: [PATCH 07/58] feat(ftrack): fix session missing --- pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py index 05908cb2da..a0059c55a6 100644 --- a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py +++ b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py @@ -43,7 +43,8 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): if "hierarchyContext" not in context.data: return - project_name = context.data["projectEntity"]["name"] + self.session = self.context.data["ftrackSession"] + project_name = self.context.data["projectEntity"]["name"] query = 'Project where full_name is "{}"'.format(project_name) project = self.session.query(query).one() auto_sync_state = project[ @@ -53,7 +54,6 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): io.install() self.ft_project = None - self.session = context.data["ftrackSession"] input_data = context.data["hierarchyContext"] From ad25719c79802fc966a801dc5b965d5ef91f8a2e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 21 Jul 2020 17:04:16 +0200 Subject: [PATCH 08/58] minor preparetion tweaks --- pype/modules/ftrack/lib/ftrack_app_handler.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pype/modules/ftrack/lib/ftrack_app_handler.py b/pype/modules/ftrack/lib/ftrack_app_handler.py index efc2df0ea5..6a40c1a382 100644 --- a/pype/modules/ftrack/lib/ftrack_app_handler.py +++ b/pype/modules/ftrack/lib/ftrack_app_handler.py @@ -142,6 +142,9 @@ class AppAction(BaseAction): """ entity = entities[0] + + task_name = entity["name"] + project_name = entity["project"]["full_name"] database = pypelib.get_avalon_database() @@ -164,7 +167,7 @@ class AppAction(BaseAction): "name": entity["project"]["full_name"], "code": entity["project"]["name"] }, - "task": entity["name"], + "task": task_name, "asset": asset_name, "app": host_name, "hierarchy": hierarchy @@ -210,8 +213,8 @@ class AppAction(BaseAction): prep_env.update({ "AVALON_PROJECT": project_name, "AVALON_ASSET": asset_name, - "AVALON_TASK": entity["name"], - "AVALON_APP": self.identifier.split("_")[0], + "AVALON_TASK": task_name, + "AVALON_APP": host_name, "AVALON_APP_NAME": self.identifier, "AVALON_HIERARCHY": hierarchy, "AVALON_WORKDIR": workdir From d758d93b507b32fbac56bc82763105419f6bee84 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 21 Jul 2020 17:05:01 +0200 Subject: [PATCH 09/58] ftrack app handler is using `should_start_last_workfile` from avalon and sets variables the same way --- pype/modules/ftrack/lib/ftrack_app_handler.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/pype/modules/ftrack/lib/ftrack_app_handler.py b/pype/modules/ftrack/lib/ftrack_app_handler.py index 6a40c1a382..7ba7ad3f86 100644 --- a/pype/modules/ftrack/lib/ftrack_app_handler.py +++ b/pype/modules/ftrack/lib/ftrack_app_handler.py @@ -8,7 +8,9 @@ import getpass from pype import lib as pypelib from pype.api import config, Anatomy from .ftrack_action_handler import BaseAction -from avalon.api import last_workfile, HOST_WORKFILE_EXTENSIONS +from avalon.api import ( + last_workfile, HOST_WORKFILE_EXTENSIONS, should_start_last_workfile +) class AppAction(BaseAction): @@ -219,8 +221,22 @@ class AppAction(BaseAction): "AVALON_HIERARCHY": hierarchy, "AVALON_WORKDIR": workdir }) - if last_workfile_path and os.path.exists(last_workfile_path): + + start_last_workfile = should_start_last_workfile( + project_name, host_name, task_name + ) + # Store boolean as "0"(False) or "1"(True) + prep_env["AVALON_OPEN_LAST_WORKFILE"] = ( + str(int(bool(start_last_workfile))) + ) + + if ( + start_last_workfile + and last_workfile_path + and os.path.exists(last_workfile_path) + ): prep_env["AVALON_LAST_WORKFILE"] = last_workfile_path + prep_env.update(anatomy.roots_obj.root_environments()) # collect all parents from the task From c6f5eb6b4ad1045ae7a981b4e15f014e1ecddef6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 14:58:51 +0200 Subject: [PATCH 10/58] removed icon attribute from server's sync to avalon action --- pype/modules/ftrack/events/action_sync_to_avalon.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/pype/modules/ftrack/events/action_sync_to_avalon.py b/pype/modules/ftrack/events/action_sync_to_avalon.py index a06b825d6a..4e119228c3 100644 --- a/pype/modules/ftrack/events/action_sync_to_avalon.py +++ b/pype/modules/ftrack/events/action_sync_to_avalon.py @@ -1,10 +1,8 @@ -import os import time import traceback from pype.modules.ftrack import BaseAction from pype.modules.ftrack.lib.avalon_sync import SyncEntitiesFactory -from pype.api import config class SyncToAvalonServer(BaseAction): @@ -38,17 +36,6 @@ class SyncToAvalonServer(BaseAction): variant = "- Sync To Avalon (Server)" #: Action description. description = "Send data from Ftrack to Avalon" - #: Action icon. - icon = "{}/ftrack/action_icons/PypeAdmin.svg".format( - os.environ.get( - "PYPE_STATICS_SERVER", - "http://localhost:{}".format( - config.get_presets().get("services", {}).get( - "rest_api", {} - ).get("default_port", 8021) - ) - ) - ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) From 0fc014e12f151f326f8a3e6b0cf71a3ab4b23996 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 16:49:48 +0200 Subject: [PATCH 11/58] add contants to clockify module --- pype/modules/clockify/constants.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 pype/modules/clockify/constants.py diff --git a/pype/modules/clockify/constants.py b/pype/modules/clockify/constants.py new file mode 100644 index 0000000000..5603bbc641 --- /dev/null +++ b/pype/modules/clockify/constants.py @@ -0,0 +1,17 @@ +import os +import appdirs + + +CLOCKIFY_FTRACK_SERVER_PATH = os.path.join( + os.path.dirname(__file__), "ftrack", "server" +) +CLOCKIFY_FTRACK_USER_PATH = os.path.join( + os.path.dirname(__file__), "ftrack", "user" +) +CREDENTIALS_PATH = os.path.normpath(os.path.join( + appdirs.user_data_dir("pype-app", "pype"), + "clockify.json" +)) + +ADMIN_PERMISSION_NAMES = ["WORKSPACE_OWN", "WORKSPACE_ADMIN"] +CLOCKIFY_ENDPOINT = "https://api.clockify.me/api/" From 7635c680170cf1b162a7314771021ffb07bfa126 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 16:50:37 +0200 Subject: [PATCH 12/58] moved clockify action to ftrack/user/ structure --- .../{ftrack_actions => ftrack/user}/action_clockify_sync.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pype/modules/clockify/{ftrack_actions => ftrack/user}/action_clockify_sync.py (100%) diff --git a/pype/modules/clockify/ftrack_actions/action_clockify_sync.py b/pype/modules/clockify/ftrack/user/action_clockify_sync.py similarity index 100% rename from pype/modules/clockify/ftrack_actions/action_clockify_sync.py rename to pype/modules/clockify/ftrack/user/action_clockify_sync.py From a021deb5635106ec64921d6d1191292ad8ba6856 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 16:50:56 +0200 Subject: [PATCH 13/58] created copy of clockify sync action to server folder --- .../ftrack/server/action_clockify_sync.py | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 pype/modules/clockify/ftrack/server/action_clockify_sync.py diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync.py b/pype/modules/clockify/ftrack/server/action_clockify_sync.py new file mode 100644 index 0000000000..0ba4c3a265 --- /dev/null +++ b/pype/modules/clockify/ftrack/server/action_clockify_sync.py @@ -0,0 +1,122 @@ +import json +from pype.modules.ftrack.lib import BaseAction, statics_icon +from pype.modules.clockify import ClockifyAPI + + +class SyncClocify(BaseAction): + '''Synchronise project names and task types.''' + + #: Action identifier. + identifier = 'clockify.sync' + #: Action label. + label = 'Sync To Clockify' + #: Action description. + description = 'Synchronise data to Clockify workspace' + #: roles that are allowed to register this action + role_list = ["Pypeclub", "Administrator", "project Manager"] + #: icon + icon = statics_icon("app_icons", "clockify-white.png") + + #: CLockifyApi + clockapi = ClockifyAPI() + + def discover(self, session, entities, event): + if ( + len(entities) == 1 + and entities[0].entity_type.lower() == "project" + ): + return True + return False + + def launch(self, session, entities, event): + self.clockapi.set_api() + if self.clockapi.workspace_id is None: + return { + "success": False, + "message": "Clockify Workspace or API key are not set!" + } + + if self.clockapi.validate_workspace_perm() is False: + return { + "success": False, + "message": "Missing permissions for this action!" + } + + # JOB SETTINGS + userId = event['source']['user']['id'] + user = session.query('User where id is ' + userId).one() + + job = session.create('Job', { + 'user': user, + 'status': 'running', + 'data': json.dumps({ + 'description': 'Sync Ftrack to Clockify' + }) + }) + session.commit() + + project_entity = entities[0] + if project_entity.entity_type.lower() != "project": + project_entity = self.get_project_from_entity(project_entity) + + project_name = project_entity["full_name"] + self.log.info( + "Synchronization of project \"{}\" to clockify begins.".format( + project_name + ) + ) + task_types = ( + project_entity["project_schema"]["_task_type_schema"]["types"] + ) + task_type_names = [ + task_type["name"] for task_type in task_types + ] + try: + clockify_projects = self.clockapi.get_projects() + if project_name not in clockify_projects: + response = self.clockapi.add_project(project_name) + if "id" not in response: + self.log.warning( + "Project \"{}\" can't be created. Response: {}".format( + project_name, response + ) + ) + return { + "success": False, + "message": ( + "Can't create clockify project \"{}\"." + " Unexpected error." + ).format(project_name) + } + + clockify_workspace_tags = self.clockapi.get_tags() + for task_type_name in task_type_names: + if task_type_name in clockify_workspace_tags: + self.log.debug( + "Task \"{}\" already exist".format(task_type_name) + ) + continue + + response = self.clockapi.add_tag(task_type_name) + if "id" not in response: + self.log.warning( + "Task \"{}\" can't be created. Response: {}".format( + task_type_name, response + ) + ) + + job["status"] = "done" + + except Exception: + pass + + finally: + if job["status"] != "done": + job["status"] = "failed" + session.commit() + + return True + + +def register(session, **kw): + SyncClocify(session).register() From 433a67b2dacf4e1d9c29e90cebc698af6c14ae65 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 16:52:56 +0200 Subject: [PATCH 14/58] ClockifyApi is not singleton anymore --- pype/modules/clockify/clockify_api.py | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/pype/modules/clockify/clockify_api.py b/pype/modules/clockify/clockify_api.py index 86365a9352..d8df2d8990 100644 --- a/pype/modules/clockify/clockify_api.py +++ b/pype/modules/clockify/clockify_api.py @@ -6,29 +6,7 @@ import datetime import appdirs -class Singleton(type): - _instances = {} - - def __call__(cls, *args, **kwargs): - if cls not in cls._instances: - cls._instances[cls] = super( - Singleton, cls - ).__call__(*args, **kwargs) - return cls._instances[cls] - - -class ClockifyAPI(metaclass=Singleton): - endpoint = "https://api.clockify.me/api/" - headers = {"X-Api-Key": None} - app_dir = os.path.normpath(appdirs.user_data_dir('pype-app', 'pype')) - file_name = 'clockify.json' - fpath = os.path.join(app_dir, file_name) - admin_permission_names = ['WORKSPACE_OWN', 'WORKSPACE_ADMIN'] - master_parent = None - workspace = None - workspace_id = None - - def set_master(self, master_parent): +class ClockifyAPI: self.master_parent = master_parent def verify_api(self): From b793e8bd79ce4d58031d45987c32cfcde9f65a58 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 16:55:57 +0200 Subject: [PATCH 15/58] using endpoint constant in clockify api --- pype/modules/clockify/clockify_api.py | 38 +++++++++++++-------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/pype/modules/clockify/clockify_api.py b/pype/modules/clockify/clockify_api.py index d8df2d8990..eafe95e3bd 100644 --- a/pype/modules/clockify/clockify_api.py +++ b/pype/modules/clockify/clockify_api.py @@ -3,7 +3,7 @@ import re import requests import json import datetime -import appdirs +from . import CLOCKIFY_ENDPOINT class ClockifyAPI: @@ -31,7 +31,7 @@ class ClockifyAPI: test_headers = {'X-Api-Key': api_key} action_url = 'workspaces/' response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=test_headers ) if response.status_code != 200: @@ -48,7 +48,7 @@ class ClockifyAPI: workspace_id, user_id ) response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) user_permissions = response.json() @@ -60,7 +60,7 @@ class ClockifyAPI: def get_user_id(self): action_url = 'v1/user/' response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) # this regex is neccessary: UNICODE strings are crashing @@ -120,7 +120,7 @@ class ClockifyAPI: def get_workspaces(self): action_url = 'workspaces/' response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) return { @@ -132,7 +132,7 @@ class ClockifyAPI: workspace_id = self.workspace_id action_url = 'workspaces/{}/projects/'.format(workspace_id) response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) @@ -147,7 +147,7 @@ class ClockifyAPI: workspace_id, project_id ) response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) @@ -158,7 +158,7 @@ class ClockifyAPI: workspace_id = self.workspace_id action_url = 'workspaces/{}/tags/'.format(workspace_id) response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) @@ -173,7 +173,7 @@ class ClockifyAPI: workspace_id, project_id ) response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) @@ -255,7 +255,7 @@ class ClockifyAPI: "tagIds": tag_ids } response = requests.post( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, json=body ) @@ -272,7 +272,7 @@ class ClockifyAPI: workspace_id ) response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) try: @@ -302,7 +302,7 @@ class ClockifyAPI: "end": self.get_current_time() } response = requests.put( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, json=body ) @@ -315,7 +315,7 @@ class ClockifyAPI: workspace_id = self.workspace_id action_url = 'workspaces/{}/timeEntries/'.format(workspace_id) response = requests.get( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) return response.json()[:quantity] @@ -327,7 +327,7 @@ class ClockifyAPI: workspace_id, tid ) response = requests.delete( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers ) return response.json() @@ -348,7 +348,7 @@ class ClockifyAPI: "billable": "true" } response = requests.post( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, json=body ) @@ -358,7 +358,7 @@ class ClockifyAPI: action_url = 'workspaces/' body = {"name": name} response = requests.post( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, json=body ) @@ -377,7 +377,7 @@ class ClockifyAPI: "projectId": project_id } response = requests.post( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, json=body ) @@ -391,7 +391,7 @@ class ClockifyAPI: "name": name } response = requests.post( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, json=body ) @@ -406,7 +406,7 @@ class ClockifyAPI: workspace_id, project_id ) response = requests.delete( - self.endpoint + action_url, + CLOCKIFY_ENDPOINT + action_url, headers=self.headers, ) return response.json() From a0eba1199bbb27e46b4efe26903a44f3c3b70342 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 16:58:10 +0200 Subject: [PATCH 16/58] added more constants in usage --- pype/modules/clockify/clockify_api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/modules/clockify/clockify_api.py b/pype/modules/clockify/clockify_api.py index eafe95e3bd..c4eb3bbba0 100644 --- a/pype/modules/clockify/clockify_api.py +++ b/pype/modules/clockify/clockify_api.py @@ -53,7 +53,7 @@ class ClockifyAPI: ) user_permissions = response.json() for perm in user_permissions: - if perm['name'] in self.admin_permission_names: + if perm['name'] in ADMIN_PERMISSION_NAMES: return True return False @@ -102,18 +102,18 @@ class ClockifyAPI: def get_api_key(self): api_key = None try: - file = open(self.fpath, 'r') + file = open(CREDENTIALS_JSON_PATH, 'r') api_key = json.load(file).get('api_key', None) if api_key == '': api_key = None except Exception: - file = open(self.fpath, 'w') + file = open(CREDENTIALS_JSON_PATH, 'w') file.close() return api_key def save_api_key(self, api_key): data = {'api_key': api_key} - file = open(self.fpath, 'w') + file = open(CREDENTIALS_JSON_PATH, 'w') file.write(json.dumps(data)) file.close() From 50a1545d0f8cb6a811c5f2e9c895e895b1ebc31c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 16:58:29 +0200 Subject: [PATCH 17/58] removed attributes are in init now --- pype/modules/clockify/clockify_api.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pype/modules/clockify/clockify_api.py b/pype/modules/clockify/clockify_api.py index c4eb3bbba0..8ebed302fe 100644 --- a/pype/modules/clockify/clockify_api.py +++ b/pype/modules/clockify/clockify_api.py @@ -3,11 +3,15 @@ import re import requests import json import datetime -from . import CLOCKIFY_ENDPOINT +from . import CLOCKIFY_ENDPOINT, ADMIN_PERMISSION_NAMES, CREDENTIALS_JSON_PATH class ClockifyAPI: + def __init__(self, workspace_name=None, api_key=None, master_parent=None): + self.workspace_name = workspace_name self.master_parent = master_parent + self.workspace_id = None + self.headers = {"X-Api-Key": api_key} def verify_api(self): for key, value in self.headers.items(): @@ -76,9 +80,9 @@ class ClockifyAPI: def set_workspace(self, name=None): if name is None: name = os.environ.get('CLOCKIFY_WORKSPACE', None) - self.workspace = name + self.workspace_name = name self.workspace_id = None - if self.workspace is None: + if self.workspace_name is None: return try: result = self.validate_workspace() @@ -93,7 +97,7 @@ class ClockifyAPI: def validate_workspace(self, name=None): if name is None: - name = self.workspace + name = self.workspace_name all_workspaces = self.get_workspaces() if name in all_workspaces: return all_workspaces[name] From 562fabe94df095379104bd67a4b48127b71b038d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 16:58:53 +0200 Subject: [PATCH 18/58] added constants to clockify init --- pype/modules/clockify/__init__.py | 7 +++++++ pype/modules/clockify/constants.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pype/modules/clockify/__init__.py b/pype/modules/clockify/__init__.py index 0ee2189fa5..0ea58b5b00 100644 --- a/pype/modules/clockify/__init__.py +++ b/pype/modules/clockify/__init__.py @@ -1,3 +1,10 @@ +from .constants import ( + CLOCKIFY_ENDPOINT, + ADMIN_PERMISSION_NAMES, + CREDENTIALS_JSON_PATH, + CLOCKIFY_FTRACK_USER_PATH, + CLOCKIFY_FTRACK_SERVER_PATH +) from .clockify_api import ClockifyAPI from .widget_settings import ClockifySettings from .widget_message import MessageWidget diff --git a/pype/modules/clockify/constants.py b/pype/modules/clockify/constants.py index 5603bbc641..38ad4b64cf 100644 --- a/pype/modules/clockify/constants.py +++ b/pype/modules/clockify/constants.py @@ -8,7 +8,7 @@ CLOCKIFY_FTRACK_SERVER_PATH = os.path.join( CLOCKIFY_FTRACK_USER_PATH = os.path.join( os.path.dirname(__file__), "ftrack", "user" ) -CREDENTIALS_PATH = os.path.normpath(os.path.join( +CREDENTIALS_JSON_PATH = os.path.normpath(os.path.join( appdirs.user_data_dir("pype-app", "pype"), "clockify.json" )) From 0de373f94fd19bcc0421edad3918862a1377d0ba Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 16:59:46 +0200 Subject: [PATCH 19/58] formatting fix --- pype/modules/clockify/clockify.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pype/modules/clockify/clockify.py b/pype/modules/clockify/clockify.py index 02b322c1c6..247bdaf0b1 100644 --- a/pype/modules/clockify/clockify.py +++ b/pype/modules/clockify/clockify.py @@ -195,9 +195,10 @@ class ClockifyModule: ).format(project_name)) msg = ( - "Project \"{}\" is not in Clockify Workspace \"{}\"." + "Project \"{}\" is not" + " in Clockify Workspace \"{}\"." "

Please inform your Project Manager." - ).format(project_name, str(self.clockapi.workspace)) + ).format(project_name, str(self.clockapi.workspace_name)) self.message_widget = MessageWidget( self.main_parent, msg, "Clockify - Info Message" From eeb5b54cef05c91cc610e68a0406d6f022fd3410 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:00:35 +0200 Subject: [PATCH 20/58] using constant in clockify module --- pype/modules/clockify/clockify.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pype/modules/clockify/clockify.py b/pype/modules/clockify/clockify.py index 247bdaf0b1..5191727b33 100644 --- a/pype/modules/clockify/clockify.py +++ b/pype/modules/clockify/clockify.py @@ -3,11 +3,12 @@ import threading from pype.api import Logger from avalon import style from Qt import QtWidgets -from . import ClockifySettings, ClockifyAPI, MessageWidget +from . import ( + ClockifySettings, ClockifyAPI, MessageWidget, CLOCKIFY_FTRACK_USER_PATH +) class ClockifyModule: - workspace_name = None def __init__(self, main_parent=None, parent=None): @@ -50,14 +51,12 @@ class ClockifyModule: def process_modules(self, modules): if 'FtrackModule' in modules: - actions_path = os.path.sep.join([ - os.path.dirname(__file__), - 'ftrack_actions' - ]) current = os.environ.get('FTRACK_ACTIONS_PATH', '') if current: current += os.pathsep - os.environ['FTRACK_ACTIONS_PATH'] = current + actions_path + os.environ['FTRACK_ACTIONS_PATH'] = ( + current + CLOCKIFY_FTRACK_USER_PATH + ) if 'AvalonApps' in modules: from launcher import lib From 774f7953dd9b0e488f738b005370ba4334bc6b38 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:00:57 +0200 Subject: [PATCH 21/58] clockify module can handle new clockify api --- pype/modules/clockify/clockify.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pype/modules/clockify/clockify.py b/pype/modules/clockify/clockify.py index 5191727b33..4c99fb8a15 100644 --- a/pype/modules/clockify/clockify.py +++ b/pype/modules/clockify/clockify.py @@ -21,7 +21,7 @@ class ClockifyModule: self.main_parent = main_parent self.parent = parent - self.clockapi = ClockifyAPI() + self.clockapi = ClockifyAPI(master_parent=self) self.message_widget = None self.widget_settings = ClockifySettings(main_parent, self) self.widget_settings_required = None @@ -32,8 +32,6 @@ class ClockifyModule: self.bool_api_key_set = False self.bool_workspace_set = False self.bool_timer_run = False - - self.clockapi.set_master(self) self.bool_api_key_set = self.clockapi.set_api() def tray_start(self): From 489bed82a6b83bbc9ea7c6bf4631d974e894ca44 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:02:18 +0200 Subject: [PATCH 22/58] sync to clockify changed label --- pype/modules/clockify/ftrack/user/action_clockify_sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/clockify/ftrack/user/action_clockify_sync.py b/pype/modules/clockify/ftrack/user/action_clockify_sync.py index 0ba4c3a265..50ec455b13 100644 --- a/pype/modules/clockify/ftrack/user/action_clockify_sync.py +++ b/pype/modules/clockify/ftrack/user/action_clockify_sync.py @@ -9,7 +9,7 @@ class SyncClocify(BaseAction): #: Action identifier. identifier = 'clockify.sync' #: Action label. - label = 'Sync To Clockify' + label = 'Sync To Clockify (local)' #: Action description. description = 'Synchronise data to Clockify workspace' #: roles that are allowed to register this action From c840f31b598fbca6f0a6aa66499bb99316b3afa2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:06:12 +0200 Subject: [PATCH 23/58] renamed clockify action file name --- .../{action_clockify_sync.py => action_clockify_sync_local.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename pype/modules/clockify/ftrack/user/{action_clockify_sync.py => action_clockify_sync_local.py} (99%) diff --git a/pype/modules/clockify/ftrack/user/action_clockify_sync.py b/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py similarity index 99% rename from pype/modules/clockify/ftrack/user/action_clockify_sync.py rename to pype/modules/clockify/ftrack/user/action_clockify_sync_local.py index 50ec455b13..a7385c4774 100644 --- a/pype/modules/clockify/ftrack/user/action_clockify_sync.py +++ b/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py @@ -7,7 +7,7 @@ class SyncClocify(BaseAction): '''Synchronise project names and task types.''' #: Action identifier. - identifier = 'clockify.sync' + identifier = 'clockify.sync.local' #: Action label. label = 'Sync To Clockify (local)' #: Action description. From e97000b26d3a7a8c7c2fac022168b5cf03b87aa1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:06:39 +0200 Subject: [PATCH 24/58] renamed server action --- .../{action_clockify_sync.py => action_clockify_sync_server.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pype/modules/clockify/ftrack/server/{action_clockify_sync.py => action_clockify_sync_server.py} (100%) diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync.py b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py similarity index 100% rename from pype/modules/clockify/ftrack/server/action_clockify_sync.py rename to pype/modules/clockify/ftrack/server/action_clockify_sync_server.py From 789f965defd60fffa012b55d30ff73420a165de5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:07:07 +0200 Subject: [PATCH 25/58] initial modifications of clockify server action --- .../server/action_clockify_sync_server.py | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py index 0ba4c3a265..f09b1cc746 100644 --- a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py +++ b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py @@ -1,23 +1,16 @@ import json -from pype.modules.ftrack.lib import BaseAction, statics_icon +from pype.modules.ftrack.lib import BaseAction from pype.modules.clockify import ClockifyAPI -class SyncClocify(BaseAction): +class SyncClocifyServer(BaseAction): '''Synchronise project names and task types.''' - #: Action identifier. - identifier = 'clockify.sync' - #: Action label. - label = 'Sync To Clockify' - #: Action description. - description = 'Synchronise data to Clockify workspace' - #: roles that are allowed to register this action + identifier = "clockify.sync.server" + label = "Sync To Clockify (server)" + description = "Synchronise data to Clockify workspace" role_list = ["Pypeclub", "Administrator", "project Manager"] - #: icon - icon = statics_icon("app_icons", "clockify-white.png") - #: CLockifyApi clockapi = ClockifyAPI() def discover(self, session, entities, event): @@ -28,6 +21,18 @@ class SyncClocify(BaseAction): return True return False + def register(self): + self.session.event_hub.subscribe( + "topic=ftrack.action.discover", + self._discover, + priority=self.priority + ) + + launch_subscription = ( + "topic=ftrack.action.launch and data.actionIdentifier={}" + ).format(self.identifier) + self.session.event_hub.subscribe(launch_subscription, self._launch) + def launch(self, session, entities, event): self.clockapi.set_api() if self.clockapi.workspace_id is None: From e38de6ed581d77f89b4242c0a5b30e63a1bb943e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:09:24 +0200 Subject: [PATCH 26/58] allow discovering only for discover roles --- .../server/action_clockify_sync_server.py | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py index f09b1cc746..19deb05e6d 100644 --- a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py +++ b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py @@ -9,16 +9,30 @@ class SyncClocifyServer(BaseAction): identifier = "clockify.sync.server" label = "Sync To Clockify (server)" description = "Synchronise data to Clockify workspace" - role_list = ["Pypeclub", "Administrator", "project Manager"] + + discover_role_list = ["Pypeclub", "Administrator", "project Manager"] clockapi = ClockifyAPI() def discover(self, session, entities, event): if ( - len(entities) == 1 - and entities[0].entity_type.lower() == "project" + len(entities) != 1 + or entities[0].entity_type.lower() == "project" ): - return True + return False + + # Get user and check his roles + user_id = event.get("source", {}).get("user", {}).get("id") + if not user_id: + return False + + user = session.query("User where id is \"{}\"".format(user_id)).first() + if not user: + return False + + for role in user["user_security_roles"]: + if role["security_role"]["name"] in self.discover_role_list: + return True return False def register(self): @@ -124,4 +138,4 @@ class SyncClocifyServer(BaseAction): def register(session, **kw): - SyncClocify(session).register() + SyncClocifyServer(session).register() From de9a075f04b24afea517daeaf4486a68e855fc5c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:25:59 +0200 Subject: [PATCH 27/58] modified headers access in clockify --- pype/modules/clockify/clockify_api.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pype/modules/clockify/clockify_api.py b/pype/modules/clockify/clockify_api.py index 8ebed302fe..a8eefe13a4 100644 --- a/pype/modules/clockify/clockify_api.py +++ b/pype/modules/clockify/clockify_api.py @@ -11,7 +11,11 @@ class ClockifyAPI: self.workspace_name = workspace_name self.master_parent = master_parent self.workspace_id = None - self.headers = {"X-Api-Key": api_key} + self.api_key = api_key + + @property + def headers(self): + return {"X-Api-Key": self.api_key} def verify_api(self): for key, value in self.headers.items(): @@ -24,7 +28,7 @@ class ClockifyAPI: api_key = self.get_api_key() if api_key is not None and self.validate_api_key(api_key) is True: - self.headers["X-Api-Key"] = api_key + self.api_key = api_key self.set_workspace() if self.master_parent: self.master_parent.signed_in() From 8dfca2378c9a0653c76f4713f50b18943822647d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:26:13 +0200 Subject: [PATCH 28/58] minor tweaks --- .../server/action_clockify_sync_server.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py index 19deb05e6d..7e3c266b72 100644 --- a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py +++ b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py @@ -62,15 +62,13 @@ class SyncClocifyServer(BaseAction): } # JOB SETTINGS - userId = event['source']['user']['id'] - user = session.query('User where id is ' + userId).one() + user_id = event["source"]["user"]["id"] + user = session.query("User where id is " + user_id).one() - job = session.create('Job', { - 'user': user, - 'status': 'running', - 'data': json.dumps({ - 'description': 'Sync Ftrack to Clockify' - }) + job = session.create("Job", { + "user": user, + "status": "running", + "data": json.dumps({"description": "Sync Ftrack to Clockify"}) }) session.commit() @@ -127,7 +125,10 @@ class SyncClocifyServer(BaseAction): job["status"] = "done" except Exception: - pass + self.log.warning( + "Synchronization to clockify failed.", + exc_info=True + ) finally: if job["status"] != "done": From 4f244d187b75677435161aa6790ef4542a4a7df0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:48:34 +0200 Subject: [PATCH 29/58] clockify api is set in initialization part of action on server --- .../server/action_clockify_sync_server.py | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py index 7e3c266b72..0a844c9ef5 100644 --- a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py +++ b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py @@ -1,3 +1,4 @@ +import os import json from pype.modules.ftrack.lib import BaseAction from pype.modules.clockify import ClockifyAPI @@ -12,7 +13,32 @@ class SyncClocifyServer(BaseAction): discover_role_list = ["Pypeclub", "Administrator", "project Manager"] - clockapi = ClockifyAPI() + def __init__(self, *args, **kwargs): + super(SyncClocifyServer, self).__init__(*args, **kwargs) + + self.workspace_name = os.environ.get("CLOCKIFY_WORKSPACE") + api_key = os.environ.get("CLOCKIFY_API_KEY") + self.clockapi = ClockifyAPI(self.workspace_name, api_key) + self.api_key = api_key + + if api_key is None: + modified_key = "None" + else: + str_len = int(len(api_key) / 2) + start_replace = int(len(api_key) / 4) + modified_key = "" + for idx in range(len(api_key)): + if idx >= start_replace and idx < start_replace + str_len: + replacement = "X" + else: + replacement = api_key[idx] + modified_key += replacement + + self.log.info( + "Clockify info. Workspace: \"{}\" API key: \"{}\"".format( + str(self.workspace_name), str(modified_key) + ) + ) def discover(self, session, entities, event): if ( @@ -48,7 +74,6 @@ class SyncClocifyServer(BaseAction): self.session.event_hub.subscribe(launch_subscription, self._launch) def launch(self, session, entities, event): - self.clockapi.set_api() if self.clockapi.workspace_id is None: return { "success": False, From eb30a53af8c6327a9f3d5d2f642a74032527147a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 17:49:12 +0200 Subject: [PATCH 30/58] changed class name --- .../clockify/ftrack/user/action_clockify_sync_local.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py b/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py index a7385c4774..528614eeba 100644 --- a/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py +++ b/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py @@ -3,7 +3,7 @@ from pype.modules.ftrack.lib import BaseAction, statics_icon from pype.modules.clockify import ClockifyAPI -class SyncClocify(BaseAction): +class SyncClocifyLocal(BaseAction): '''Synchronise project names and task types.''' #: Action identifier. @@ -119,4 +119,4 @@ class SyncClocify(BaseAction): def register(session, **kw): - SyncClocify(session).register() + SyncClocifyLocal(session).register() From 98c1587da7ad6f26d517526e7c8f534d9f35f77e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 18:02:50 +0200 Subject: [PATCH 31/58] added clockify registration to ftrack event processor --- .../ftrack_server/sub_event_processor.py | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/pype/modules/ftrack/ftrack_server/sub_event_processor.py b/pype/modules/ftrack/ftrack_server/sub_event_processor.py index d7bb7a53b3..de0f0459a9 100644 --- a/pype/modules/ftrack/ftrack_server/sub_event_processor.py +++ b/pype/modules/ftrack/ftrack_server/sub_event_processor.py @@ -9,7 +9,7 @@ from pype.modules.ftrack.ftrack_server.lib import ( SocketSession, ProcessEventHub, TOPIC_STATUS_SERVER ) import ftrack_api -from pype.api import Logger +from pype.api import Logger, config log = Logger().get_logger("Event processor") @@ -55,6 +55,42 @@ def register(session): ) +def clockify_module_registration(): + module_name = "Clockify" + + menu_items = config.get_presets()["tray"]["menu_items"] + if not menu_items["item_usage"][module_name]: + return + + api_key = os.environ.get("CLOCKIFY_API_KEY") + if not api_key: + log.warning("Clockify API key is not set.") + return + + workspace_name = os.environ.get("CLOCKIFY_WORKSPACE") + if not workspace_name: + workspace_name = ( + menu_items + .get("attributes", {}) + .get(module_name, {}) + .get("workspace_name", {}) + ) + + if not workspace_name: + log.warning("Clockify Workspace is not set.") + return + + os.environ["CLOCKIFY_WORKSPACE"] = workspace_name + + from pype.modules.clockify import CLOCKIFY_FTRACK_SERVER_PATH + + current = os.environ.get("FTRACK_EVENTS_PATH") or "" + if current: + current += os.pathsep + os.environ["FTRACK_EVENTS_PATH"] = current + CLOCKIFY_FTRACK_SERVER_PATH + return True + + def main(args): port = int(args[-1]) # Create a TCP/IP socket @@ -66,6 +102,11 @@ def main(args): sock.connect(server_address) sock.sendall(b"CreatedProcess") + try: + clockify_result = clockify_module_registration() + except Exception: + log.info("Clockify registration failed.", exc_info=True) + try: session = SocketSession( auto_connect_event_hub=True, sock=sock, Eventhub=ProcessEventHub From eb4153a7865a7ebe941c2a3f076cde680e05c9fc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 18:08:03 +0200 Subject: [PATCH 32/58] added clockifyapikey and clockifyworkspace arguments to ftrack event server script --- .../ftrack/ftrack_server/event_server_cli.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pype/modules/ftrack/ftrack_server/event_server_cli.py b/pype/modules/ftrack/ftrack_server/event_server_cli.py index 73c7abfc5d..bf51c37290 100644 --- a/pype/modules/ftrack/ftrack_server/event_server_cli.py +++ b/pype/modules/ftrack/ftrack_server/event_server_cli.py @@ -522,6 +522,21 @@ def main(argv): help="Load creadentials from apps dir", action="store_true" ) + parser.add_argument( + "-clockifyapikey", type=str, + help=( + "Enter API key for Clockify actions." + " (default from environment: $CLOCKIFY_API_KEY)" + ) + ) + parser.add_argument( + "-clockifyworkspace", type=str, + help=( + "Enter workspace for Clockify." + " (default from module presets or " + "environment: $CLOCKIFY_WORKSPACE)" + ) + ) ftrack_url = os.environ.get('FTRACK_SERVER') username = os.environ.get('FTRACK_API_USER') api_key = os.environ.get('FTRACK_API_KEY') @@ -546,6 +561,12 @@ def main(argv): if kwargs.ftrackapikey: api_key = kwargs.ftrackapikey + if kwargs.clockifyworkspace: + os.environ["CLOCKIFY_WORKSPACE"] = kwargs.clockifyworkspace + + if kwargs.clockifyapikey: + os.environ["CLOCKIFY_API_KEY"] = kwargs.clockifyapikey + legacy = kwargs.legacy # Check url regex and accessibility ftrack_url = check_ftrack_url(ftrack_url) From ec9ff173379cf6601414445bac41b621272a4a16 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 18:32:45 +0200 Subject: [PATCH 33/58] removed workspace_name from clockify api init --- pype/modules/clockify/clockify_api.py | 8 ++++---- .../ftrack/server/action_clockify_sync_server.py | 9 ++++----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/pype/modules/clockify/clockify_api.py b/pype/modules/clockify/clockify_api.py index a8eefe13a4..ca642a1c21 100644 --- a/pype/modules/clockify/clockify_api.py +++ b/pype/modules/clockify/clockify_api.py @@ -7,10 +7,10 @@ from . import CLOCKIFY_ENDPOINT, ADMIN_PERMISSION_NAMES, CREDENTIALS_JSON_PATH class ClockifyAPI: - def __init__(self, workspace_name=None, api_key=None, master_parent=None): - self.workspace_name = workspace_name - self.master_parent = master_parent + def __init__(self, api_key=None, master_parent=None): + self.workspace_name = None self.workspace_id = None + self.master_parent = master_parent self.api_key = api_key @property @@ -73,7 +73,7 @@ class ClockifyAPI: ) # this regex is neccessary: UNICODE strings are crashing # during json serialization - id_regex ='\"{1}id\"{1}\:{1}\"{1}\w+\"{1}' + id_regex = '\"{1}id\"{1}\:{1}\"{1}\w+\"{1}' result = re.findall(id_regex, str(response.content)) if len(result) != 1: # replace with log and better message? diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py index 0a844c9ef5..442d1f92df 100644 --- a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py +++ b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py @@ -16,11 +16,10 @@ class SyncClocifyServer(BaseAction): def __init__(self, *args, **kwargs): super(SyncClocifyServer, self).__init__(*args, **kwargs) - self.workspace_name = os.environ.get("CLOCKIFY_WORKSPACE") + workspace_name = os.environ.get("CLOCKIFY_WORKSPACE") api_key = os.environ.get("CLOCKIFY_API_KEY") - self.clockapi = ClockifyAPI(self.workspace_name, api_key) - self.api_key = api_key - + self.clockapi = ClockifyAPI(api_key) + self.clockapi.set_workspace(workspace_name) if api_key is None: modified_key = "None" else: @@ -36,7 +35,7 @@ class SyncClocifyServer(BaseAction): self.log.info( "Clockify info. Workspace: \"{}\" API key: \"{}\"".format( - str(self.workspace_name), str(modified_key) + str(workspace_name), str(modified_key) ) ) From 2cfe2e139307236af803428ae33cffe87d6f5b0e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 18:32:58 +0200 Subject: [PATCH 34/58] fix discover condition --- .../clockify/ftrack/server/action_clockify_sync_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py index 442d1f92df..94b2f21da4 100644 --- a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py +++ b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py @@ -42,7 +42,7 @@ class SyncClocifyServer(BaseAction): def discover(self, session, entities, event): if ( len(entities) != 1 - or entities[0].entity_type.lower() == "project" + or entities[0].entity_type.lower() != "project" ): return False From 0318a5c35cf4829843dbfee5acd13428fab51aba Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 18:44:18 +0200 Subject: [PATCH 35/58] removed unused variable --- pype/modules/ftrack/ftrack_server/sub_event_processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/ftrack/ftrack_server/sub_event_processor.py b/pype/modules/ftrack/ftrack_server/sub_event_processor.py index de0f0459a9..baef2ec5f6 100644 --- a/pype/modules/ftrack/ftrack_server/sub_event_processor.py +++ b/pype/modules/ftrack/ftrack_server/sub_event_processor.py @@ -103,7 +103,7 @@ def main(args): sock.sendall(b"CreatedProcess") try: - clockify_result = clockify_module_registration() + clockify_module_registration() except Exception: log.info("Clockify registration failed.", exc_info=True) From f7740839b7806a4c0979a9378349b746392796e7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 18:54:33 +0200 Subject: [PATCH 36/58] removed kredenc part from code --- pype/hosts/maya/customize.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pype/hosts/maya/customize.py b/pype/hosts/maya/customize.py index cf179e1d63..c929de22b8 100644 --- a/pype/hosts/maya/customize.py +++ b/pype/hosts/maya/customize.py @@ -145,16 +145,6 @@ def override_toolbox_ui(): parent=parent) controls.append(control) - # control = mc.iconTextButton( - # "pype_toolbox", - # annotation="Kredenc", - # label="Kredenc", - # image=os.path.join(icons, "kredenc_logo.png"), - # bgc=background_color, - # width=icon_size, - # height=icon_size, - # parent=parent) - # controls.append(control) # Add the buttons on the bottom and stack # them above each other with side padding From f7aa0a83da9994971deb533ff86e2807c58d00cc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 18:56:20 +0200 Subject: [PATCH 37/58] all tools are tried to import --- pype/hosts/maya/customize.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/pype/hosts/maya/customize.py b/pype/hosts/maya/customize.py index c929de22b8..bd0901a6fd 100644 --- a/pype/hosts/maya/customize.py +++ b/pype/hosts/maya/customize.py @@ -69,11 +69,30 @@ def override_component_mask_commands(): def override_toolbox_ui(): """Add custom buttons in Toolbox as replacement for Maya web help icon.""" + inventory = None + loader = None + launch_workfiles_app = None + mayalookassigner = None + try: + import avalon.tools.sceneinventory as inventory + except Exception: + log.warning("Could not import SceneInventory tool") + + try: + import avalon.tools.loader as loader + except Exception: + log.warning("Could not import Loader tool") + + try: + from avalon.maya.pipeline import launch_workfiles_app + except Exception: + log.warning("Could not import Workfiles tool") + + try: + import mayalookassigner + except Exception: + log.warning("Could not import Maya Look assigner tool") - import avalon.tools.sceneinventory as inventory - import avalon.tools.loader as loader - from avalon.maya.pipeline import launch_workfiles_app - import mayalookassigner from pype.api import resources icons = resources.get_resource("icons") From af26c083e549e5e5851b1415aed668bcfffc69ac Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 18:56:41 +0200 Subject: [PATCH 38/58] only imported tools are added to controls --- pype/hosts/maya/customize.py | 102 +++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/pype/hosts/maya/customize.py b/pype/hosts/maya/customize.py index bd0901a6fd..c6b5ac63cf 100644 --- a/pype/hosts/maya/customize.py +++ b/pype/hosts/maya/customize.py @@ -115,55 +115,65 @@ def override_toolbox_ui(): # Create our controls background_color = (0.267, 0.267, 0.267) controls = [] + if mayalookassigner: + controls.append( + mc.iconTextButton( + "pype_toolbox_lookmanager", + annotation="Look Manager", + label="Look Manager", + image=os.path.join(icons, "lookmanager.png"), + command=lambda: mayalookassigner.show(), + bgc=background_color, + width=icon_size, + height=icon_size, + parent=parent + ) + ) - control = mc.iconTextButton( - "pype_toolbox_lookmanager", - annotation="Look Manager", - label="Look Manager", - image=os.path.join(icons, "lookmanager.png"), - command=lambda: mayalookassigner.show(), - bgc=background_color, - width=icon_size, - height=icon_size, - parent=parent) - controls.append(control) + if launch_workfiles_app: + controls.append( + mc.iconTextButton( + "pype_toolbox_workfiles", + annotation="Work Files", + label="Work Files", + image=os.path.join(icons, "workfiles.png"), + command=lambda: launch_workfiles_app(), + bgc=background_color, + width=icon_size, + height=icon_size, + parent=parent + ) + ) - control = mc.iconTextButton( - "pype_toolbox_workfiles", - annotation="Work Files", - label="Work Files", - image=os.path.join(icons, "workfiles.png"), - command=lambda: launch_workfiles_app(), - bgc=background_color, - width=icon_size, - height=icon_size, - parent=parent) - controls.append(control) - - control = mc.iconTextButton( - "pype_toolbox_loader", - annotation="Loader", - label="Loader", - image=os.path.join(icons, "loader.png"), - command=lambda: loader.show(use_context=True), - bgc=background_color, - width=icon_size, - height=icon_size, - parent=parent) - controls.append(control) - - control = mc.iconTextButton( - "pype_toolbox_manager", - annotation="Inventory", - label="Inventory", - image=os.path.join(icons, "inventory.png"), - command=lambda: inventory.show(), - bgc=background_color, - width=icon_size, - height=icon_size, - parent=parent) - controls.append(control) + if loader: + controls.append( + mc.iconTextButton( + "pype_toolbox_loader", + annotation="Loader", + label="Loader", + image=os.path.join(icons, "loader.png"), + command=lambda: loader.show(use_context=True), + bgc=background_color, + width=icon_size, + height=icon_size, + parent=parent + ) + ) + if inventory: + controls.append( + mc.iconTextButton( + "pype_toolbox_manager", + annotation="Inventory", + label="Inventory", + image=os.path.join(icons, "inventory.png"), + command=lambda: inventory.show(), + bgc=background_color, + width=icon_size, + height=icon_size, + parent=parent + ) + ) # Add the buttons on the bottom and stack # them above each other with side padding From 710fdd4d5b94224b500ce6b3158aa68c154d2713 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 18:57:06 +0200 Subject: [PATCH 39/58] skip adding tools process if none of tools import was succesful --- pype/hosts/maya/customize.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pype/hosts/maya/customize.py b/pype/hosts/maya/customize.py index c6b5ac63cf..ee3ad4f239 100644 --- a/pype/hosts/maya/customize.py +++ b/pype/hosts/maya/customize.py @@ -97,6 +97,11 @@ def override_toolbox_ui(): icons = resources.get_resource("icons") + if not any(( + mayalookassigner, launch_workfiles_app, loader, inventory + )): + return + # Ensure the maya web icon on toolbox exists web_button = "ToolBox|MainToolboxLayout|mayaWebButton" if not mc.iconTextButton(web_button, query=True, exists=True): From b6b3ea0f35836d364ed186cf8faf82f08247d692 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 19:56:09 +0200 Subject: [PATCH 40/58] cleaned clockify imports --- pype/modules/clockify/__init__.py | 10 ---------- pype/modules/clockify/clockify.py | 6 +++--- pype/modules/clockify/clockify_api.py | 4 +++- .../ftrack/server/action_clockify_sync_server.py | 2 +- .../clockify/ftrack/user/action_clockify_sync_local.py | 2 +- .../modules/clockify/launcher_actions/ClockifyStart.py | 2 +- pype/modules/clockify/launcher_actions/ClockifySync.py | 2 +- .../ftrack/ftrack_server/sub_event_processor.py | 2 +- 8 files changed, 11 insertions(+), 19 deletions(-) diff --git a/pype/modules/clockify/__init__.py b/pype/modules/clockify/__init__.py index 0ea58b5b00..8e11d2f5f4 100644 --- a/pype/modules/clockify/__init__.py +++ b/pype/modules/clockify/__init__.py @@ -1,13 +1,3 @@ -from .constants import ( - CLOCKIFY_ENDPOINT, - ADMIN_PERMISSION_NAMES, - CREDENTIALS_JSON_PATH, - CLOCKIFY_FTRACK_USER_PATH, - CLOCKIFY_FTRACK_SERVER_PATH -) -from .clockify_api import ClockifyAPI -from .widget_settings import ClockifySettings -from .widget_message import MessageWidget from .clockify import ClockifyModule CLASS_DEFINIION = ClockifyModule diff --git a/pype/modules/clockify/clockify.py b/pype/modules/clockify/clockify.py index 4c99fb8a15..fea15a1bea 100644 --- a/pype/modules/clockify/clockify.py +++ b/pype/modules/clockify/clockify.py @@ -3,9 +3,9 @@ import threading from pype.api import Logger from avalon import style from Qt import QtWidgets -from . import ( - ClockifySettings, ClockifyAPI, MessageWidget, CLOCKIFY_FTRACK_USER_PATH -) +from .widgets import ClockifySettings, MessageWidget +from .clockify_api import ClockifyAPI +from .constants import CLOCKIFY_FTRACK_USER_PATH class ClockifyModule: diff --git a/pype/modules/clockify/clockify_api.py b/pype/modules/clockify/clockify_api.py index ca642a1c21..0a09c65628 100644 --- a/pype/modules/clockify/clockify_api.py +++ b/pype/modules/clockify/clockify_api.py @@ -3,7 +3,9 @@ import re import requests import json import datetime -from . import CLOCKIFY_ENDPOINT, ADMIN_PERMISSION_NAMES, CREDENTIALS_JSON_PATH +from .constants import ( + CLOCKIFY_ENDPOINT, ADMIN_PERMISSION_NAMES, CREDENTIALS_JSON_PATH +) class ClockifyAPI: diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py index 94b2f21da4..ae911f6258 100644 --- a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py +++ b/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py @@ -1,7 +1,7 @@ import os import json from pype.modules.ftrack.lib import BaseAction -from pype.modules.clockify import ClockifyAPI +from pype.modules.clockify.clockify_api import ClockifyAPI class SyncClocifyServer(BaseAction): diff --git a/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py b/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py index 528614eeba..e74bf3dbb3 100644 --- a/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py +++ b/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py @@ -1,6 +1,6 @@ import json from pype.modules.ftrack.lib import BaseAction, statics_icon -from pype.modules.clockify import ClockifyAPI +from pype.modules.clockify.clockify_api import ClockifyAPI class SyncClocifyLocal(BaseAction): diff --git a/pype/modules/clockify/launcher_actions/ClockifyStart.py b/pype/modules/clockify/launcher_actions/ClockifyStart.py index d5e9164977..f97360662f 100644 --- a/pype/modules/clockify/launcher_actions/ClockifyStart.py +++ b/pype/modules/clockify/launcher_actions/ClockifyStart.py @@ -1,6 +1,6 @@ from avalon import api, io from pype.api import Logger -from pype.modules.clockify import ClockifyAPI +from pype.modules.clockify.clockify_api import ClockifyAPI log = Logger().get_logger(__name__, "clockify_start") diff --git a/pype/modules/clockify/launcher_actions/ClockifySync.py b/pype/modules/clockify/launcher_actions/ClockifySync.py index 0f20d1dce1..a77c038076 100644 --- a/pype/modules/clockify/launcher_actions/ClockifySync.py +++ b/pype/modules/clockify/launcher_actions/ClockifySync.py @@ -1,5 +1,5 @@ from avalon import api, io -from pype.modules.clockify import ClockifyAPI +from pype.modules.clockify.clockify_api import ClockifyAPI from pype.api import Logger log = Logger().get_logger(__name__, "clockify_sync") diff --git a/pype/modules/ftrack/ftrack_server/sub_event_processor.py b/pype/modules/ftrack/ftrack_server/sub_event_processor.py index baef2ec5f6..4a3241dd4f 100644 --- a/pype/modules/ftrack/ftrack_server/sub_event_processor.py +++ b/pype/modules/ftrack/ftrack_server/sub_event_processor.py @@ -82,7 +82,7 @@ def clockify_module_registration(): os.environ["CLOCKIFY_WORKSPACE"] = workspace_name - from pype.modules.clockify import CLOCKIFY_FTRACK_SERVER_PATH + from pype.modules.clockify.constants import CLOCKIFY_FTRACK_SERVER_PATH current = os.environ.get("FTRACK_EVENTS_PATH") or "" if current: From 3b0fae9fa25055e73d99086d4348d6f9d5e42b86 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 22 Jul 2020 19:56:21 +0200 Subject: [PATCH 41/58] clockify widgets are in one file --- pype/modules/clockify/widget_message.py | 92 ------------------- .../{widget_settings.py => widgets.py} | 90 +++++++++++++++++- 2 files changed, 89 insertions(+), 93 deletions(-) delete mode 100644 pype/modules/clockify/widget_message.py rename pype/modules/clockify/{widget_settings.py => widgets.py} (66%) diff --git a/pype/modules/clockify/widget_message.py b/pype/modules/clockify/widget_message.py deleted file mode 100644 index 9e4fad1df1..0000000000 --- a/pype/modules/clockify/widget_message.py +++ /dev/null @@ -1,92 +0,0 @@ -from Qt import QtCore, QtGui, QtWidgets -from avalon import style -from pype.api import resources - - -class MessageWidget(QtWidgets.QWidget): - - SIZE_W = 300 - SIZE_H = 130 - - closed = QtCore.Signal() - - def __init__(self, parent=None, messages=[], title="Message"): - - super(MessageWidget, self).__init__() - - self._parent = parent - - # Icon - if parent and hasattr(parent, 'icon'): - self.setWindowIcon(parent.icon) - else: - icon = QtGui.QIcon(resources.pype_icon_filepath()) - self.setWindowIcon(icon) - - self.setWindowFlags( - QtCore.Qt.WindowCloseButtonHint | - QtCore.Qt.WindowMinimizeButtonHint - ) - - # Font - self.font = QtGui.QFont() - self.font.setFamily("DejaVu Sans Condensed") - self.font.setPointSize(9) - self.font.setBold(True) - self.font.setWeight(50) - self.font.setKerning(True) - - # Size setting - self.resize(self.SIZE_W, self.SIZE_H) - self.setMinimumSize(QtCore.QSize(self.SIZE_W, self.SIZE_H)) - self.setMaximumSize(QtCore.QSize(self.SIZE_W+100, self.SIZE_H+100)) - - # Style - self.setStyleSheet(style.load_stylesheet()) - - self.setLayout(self._ui_layout(messages)) - self.setWindowTitle(title) - - def _ui_layout(self, messages): - if not messages: - messages = ["*Misssing messages (This is a bug)*", ] - - elif not isinstance(messages, (tuple, list)): - messages = [messages, ] - - main_layout = QtWidgets.QVBoxLayout(self) - - labels = [] - for message in messages: - label = QtWidgets.QLabel(message) - label.setFont(self.font) - label.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) - label.setTextFormat(QtCore.Qt.RichText) - label.setWordWrap(True) - - labels.append(label) - main_layout.addWidget(label) - - btn_close = QtWidgets.QPushButton("Close") - btn_close.setToolTip('Close this window') - btn_close.clicked.connect(self.on_close_clicked) - - btn_group = QtWidgets.QHBoxLayout() - btn_group.addStretch(1) - btn_group.addWidget(btn_close) - - main_layout.addLayout(btn_group) - - self.labels = labels - self.btn_group = btn_group - self.btn_close = btn_close - self.main_layout = main_layout - - return main_layout - - def on_close_clicked(self): - self.close() - - def close(self, *args, **kwargs): - self.closed.emit() - super(MessageWidget, self).close(*args, **kwargs) diff --git a/pype/modules/clockify/widget_settings.py b/pype/modules/clockify/widgets.py similarity index 66% rename from pype/modules/clockify/widget_settings.py rename to pype/modules/clockify/widgets.py index 7e5ee300bb..dc57a48ecb 100644 --- a/pype/modules/clockify/widget_settings.py +++ b/pype/modules/clockify/widgets.py @@ -1,9 +1,97 @@ -import os from Qt import QtCore, QtGui, QtWidgets from avalon import style from pype.api import resources +class MessageWidget(QtWidgets.QWidget): + + SIZE_W = 300 + SIZE_H = 130 + + closed = QtCore.Signal() + + def __init__(self, parent=None, messages=[], title="Message"): + + super(MessageWidget, self).__init__() + + self._parent = parent + + # Icon + if parent and hasattr(parent, 'icon'): + self.setWindowIcon(parent.icon) + else: + icon = QtGui.QIcon(resources.pype_icon_filepath()) + self.setWindowIcon(icon) + + self.setWindowFlags( + QtCore.Qt.WindowCloseButtonHint | + QtCore.Qt.WindowMinimizeButtonHint + ) + + # Font + self.font = QtGui.QFont() + self.font.setFamily("DejaVu Sans Condensed") + self.font.setPointSize(9) + self.font.setBold(True) + self.font.setWeight(50) + self.font.setKerning(True) + + # Size setting + self.resize(self.SIZE_W, self.SIZE_H) + self.setMinimumSize(QtCore.QSize(self.SIZE_W, self.SIZE_H)) + self.setMaximumSize(QtCore.QSize(self.SIZE_W+100, self.SIZE_H+100)) + + # Style + self.setStyleSheet(style.load_stylesheet()) + + self.setLayout(self._ui_layout(messages)) + self.setWindowTitle(title) + + def _ui_layout(self, messages): + if not messages: + messages = ["*Misssing messages (This is a bug)*", ] + + elif not isinstance(messages, (tuple, list)): + messages = [messages, ] + + main_layout = QtWidgets.QVBoxLayout(self) + + labels = [] + for message in messages: + label = QtWidgets.QLabel(message) + label.setFont(self.font) + label.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) + label.setTextFormat(QtCore.Qt.RichText) + label.setWordWrap(True) + + labels.append(label) + main_layout.addWidget(label) + + btn_close = QtWidgets.QPushButton("Close") + btn_close.setToolTip('Close this window') + btn_close.clicked.connect(self.on_close_clicked) + + btn_group = QtWidgets.QHBoxLayout() + btn_group.addStretch(1) + btn_group.addWidget(btn_close) + + main_layout.addLayout(btn_group) + + self.labels = labels + self.btn_group = btn_group + self.btn_close = btn_close + self.main_layout = main_layout + + return main_layout + + def on_close_clicked(self): + self.close() + + def close(self, *args, **kwargs): + self.closed.emit() + super(MessageWidget, self).close(*args, **kwargs) + + class ClockifySettings(QtWidgets.QWidget): SIZE_W = 300 From 77c9725c595e4e40f25f0ad00d9ec4a4e70a7b9d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Jul 2020 14:14:28 +0200 Subject: [PATCH 42/58] store project's applications to event data to not query project all the time --- pype/modules/ftrack/lib/ftrack_app_handler.py | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/pype/modules/ftrack/lib/ftrack_app_handler.py b/pype/modules/ftrack/lib/ftrack_app_handler.py index efc2df0ea5..b16dbd7b64 100644 --- a/pype/modules/ftrack/lib/ftrack_app_handler.py +++ b/pype/modules/ftrack/lib/ftrack_app_handler.py @@ -84,7 +84,7 @@ class AppAction(BaseAction): if ( len(entities) != 1 - or entities[0].entity_type.lower() != 'task' + or entities[0].entity_type.lower() != "task" ): return False @@ -92,21 +92,31 @@ class AppAction(BaseAction): if entity["parent"].entity_type.lower() == "project": return False - ft_project = self.get_project_from_entity(entity) - database = pypelib.get_avalon_database() - project_name = ft_project["full_name"] - avalon_project = database[project_name].find_one({ - "type": "project" - }) + avalon_project_apps = event["data"].get("avalon_project_apps", None) + avalon_project_doc = event["data"].get("avalon_project_doc", None) + if avalon_project_apps is None: + if avalon_project_doc is None: + ft_project = self.get_project_from_entity(entity) + database = pypelib.get_avalon_database() + project_name = ft_project["full_name"] + avalon_project_doc = database[project_name].find_one({ + "type": "project" + }) or False + event["data"]["avalon_project_doc"] = avalon_project_doc - if not avalon_project: + if not avalon_project_doc: + return False + + project_apps_config = avalon_project_doc["config"].get("apps", []) + avalon_project_apps = [ + app["name"] for app in project_apps_config + ] or False + event["data"]["avalon_project_apps"] = avalon_project_apps + + if not avalon_project_apps: return False - project_apps = avalon_project["config"].get("apps", []) - apps = [app["name"] for app in project_apps] - if self.identifier in apps: - return True - return False + return self.identifier in avalon_project_apps def _launch(self, event): entities = self._translate_event(event) From fc27d6d2dff908a41e3f45948f97d00f40770cb4 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 23 Jul 2020 15:27:37 +0200 Subject: [PATCH 43/58] render wasn't working without audio --- .../plugins/harmony/publish/extract_render.py | 23 ++++++++---- .../plugins/harmony/publish/validate_audio.py | 37 +++++++++++++++++++ 2 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 pype/plugins/harmony/publish/validate_audio.py diff --git a/pype/plugins/harmony/publish/extract_render.py b/pype/plugins/harmony/publish/extract_render.py index 7ca83d3f0f..fe1352f9f9 100644 --- a/pype/plugins/harmony/publish/extract_render.py +++ b/pype/plugins/harmony/publish/extract_render.py @@ -111,13 +111,22 @@ class ExtractRender(pyblish.api.InstancePlugin): # Generate mov. mov_path = os.path.join(path, instance.data["name"] + ".mov") - args = [ - "ffmpeg", "-y", - "-i", audio_path, - "-i", - os.path.join(path, collection.head + "%04d" + collection.tail), - mov_path - ] + if os.path.isfile(audio_path): + args = [ + "ffmpeg", "-y", + "-i", audio_path, + "-i", + os.path.join(path, collection.head + "%04d" + collection.tail), + mov_path + ] + else: + args = [ + "ffmpeg", "-y", + "-i", + os.path.join(path, collection.head + "%04d" + collection.tail), + mov_path + ] + process = subprocess.Popen( args, stdout=subprocess.PIPE, diff --git a/pype/plugins/harmony/publish/validate_audio.py b/pype/plugins/harmony/publish/validate_audio.py new file mode 100644 index 0000000000..ba113e7610 --- /dev/null +++ b/pype/plugins/harmony/publish/validate_audio.py @@ -0,0 +1,37 @@ +import json +import os + +import pyblish.api + +import avalon.harmony +import pype.hosts.harmony + + +class ValidateAudio(pyblish.api.InstancePlugin): + """Ensures that there is an audio file in the scene. If you are sure that you want to send render without audio, you can disable this validator before clicking on "publish" """ + + order = pyblish.api.ValidatorOrder + label = "Validate Audio" + families = ["render"] + hosts = ["harmony"] + optional = True + + def process(self, instance): + # Collect scene data. + func = """function func(write_node) + { + return [ + sound.getSoundtrackAll().path() + ] + } + func + """ + result = avalon.harmony.send( + {"function": func, "args": [instance[0]]} + )["result"] + + audio_path = result[0] + + msg = "You are missing audio file:\n{}".format(audio_path) + + assert os.path.isfile(audio_path), msg From cef1e9435b8f39a01351ea6b5ecf215081c98461 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 23 Jul 2020 15:27:52 +0200 Subject: [PATCH 44/58] allow loading of workfiles as templates --- pype/plugins/harmony/load/load_template_workfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/harmony/load/load_template_workfile.py b/pype/plugins/harmony/load/load_template_workfile.py index b727cf865c..db67f20ff7 100644 --- a/pype/plugins/harmony/load/load_template_workfile.py +++ b/pype/plugins/harmony/load/load_template_workfile.py @@ -9,7 +9,7 @@ from avalon import api, harmony class ImportTemplateLoader(api.Loader): """Import templates.""" - families = ["harmony.template"] + families = ["harmony.template", "workfile"] representations = ["*"] label = "Import Template" From 8437bf939292a03c5e58fd28c4f5631bc419121c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Jul 2020 16:39:52 +0100 Subject: [PATCH 45/58] correct render and publish render path passing --- .../celaction/publish/collect_render_path.py | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/pype/plugins/celaction/publish/collect_render_path.py b/pype/plugins/celaction/publish/collect_render_path.py index d5fe6c07a5..a3918a52b6 100644 --- a/pype/plugins/celaction/publish/collect_render_path.py +++ b/pype/plugins/celaction/publish/collect_render_path.py @@ -10,9 +10,14 @@ class CollectRenderPath(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.495 families = ["render.farm"] + # Presets + anatomy_render_key = None + anatomy_publish_render_key = None + def process(self, instance): anatomy = instance.context.data["anatomy"] anatomy_data = copy.deepcopy(instance.data["anatomyData"]) + anatomy_data["family"] = "render" padding = anatomy.templates.get("frame_padding", 4) anatomy_data.update({ "frame": f"%0{padding}d", @@ -21,12 +26,28 @@ class CollectRenderPath(pyblish.api.InstancePlugin): anatomy_filled = anatomy.format(anatomy_data) - render_dir = anatomy_filled["render_tmp"]["folder"] - render_path = anatomy_filled["render_tmp"]["path"] + # get anatomy rendering keys + anatomy_render_key = self.anatomy_render_key or "render" + anatomy_publish_render_key = self.anatomy_publish_render_key or "render" + + # get folder and path for rendering images from celaction + render_dir = anatomy_filled[anatomy_render_key]["folder"] + render_path = anatomy_filled[anatomy_render_key]["path"] # create dir if it doesnt exists - os.makedirs(render_dir, exist_ok=True) + try: + if not os.path.isdir(render_dir): + os.makedirs(render_dir, exist_ok=True) + except OSError: + # directory is not available + self.log.warning("Path is unreachable: `{}`".format(render_dir)) + # add rendering path to instance data instance.data["path"] = render_path + # get anatomy for published renders folder path + if anatomy_filled.get(anatomy_publish_render_key): + instance.data["publishRenderFolder"] = anatomy_filled[ + anatomy_publish_render_key]["folder"] + self.log.info(f"Render output path set to: `{render_path}`") From 7d49a281499f1ff01dc4258bd1b2b164bef13926 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Jul 2020 16:40:44 +0100 Subject: [PATCH 46/58] submit celaciton to deadline with consistent slashes in path --- pype/plugins/celaction/publish/submit_celaction_deadline.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pype/plugins/celaction/publish/submit_celaction_deadline.py b/pype/plugins/celaction/publish/submit_celaction_deadline.py index c749ec111f..9091b24150 100644 --- a/pype/plugins/celaction/publish/submit_celaction_deadline.py +++ b/pype/plugins/celaction/publish/submit_celaction_deadline.py @@ -74,6 +74,7 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): resolution_width = instance.data["resolutionWidth"] resolution_height = instance.data["resolutionHeight"] render_dir = os.path.normpath(os.path.dirname(render_path)) + render_path = os.path.normpath(render_path) script_name = os.path.basename(script_path) jobname = "%s - %s" % (script_name, instance.name) @@ -98,6 +99,7 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): args = [ f"{script_path}", "-a", + "-16", "-s ", "-e ", f"-d {render_dir}", @@ -135,8 +137,10 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): # Optional, enable double-click to preview rendered # frames from Deadline Monitor - "OutputFilename0": output_filename_0.replace("\\", "/") + "OutputFilename0": output_filename_0.replace("\\", "/"), + # # Asset dependency to wait for at least the scene file to sync. + # "AssetDependency0": script_path }, "PluginInfo": { # Input From 2e0880d90d33c6866c3fb2bdc80976823297eb60 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Jul 2020 16:41:26 +0100 Subject: [PATCH 47/58] fix(global): google drive stream file duplicity error --- pype/plugins/global/publish/integrate_new.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index d151cfc608..3582b55c82 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -551,12 +551,12 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # copy file with speedcopy and check if size of files are simetrical while True: + import shutil try: copyfile(src, dst) - except (OSError, AttributeError) as e: - self.log.warning(e) - # try it again with shutil - import shutil + except shutil.SameFileError as sfe: + self.log.critical("files are the same {} to {}".format(src, dst)) + os.remove(dst) try: shutil.copyfile(src, dst) self.log.debug("Copying files with shutil...") From ebe6bb4802a3ed39ab0ad00c6ad51e7c080cc255 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Jul 2020 16:42:12 +0100 Subject: [PATCH 48/58] fix(global): metadata file path with different anatomy key --- .../global/publish/submit_publish_job.py | 70 +++++++++++++------ 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 4f32e37c17..f74b93cf7d 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -12,7 +12,15 @@ from avalon.vendor import requests, clique import pyblish.api -def _get_script(): +def _get_script(path): + + # pass input path if exists + if path: + if os.path.exists(path): + return str(path) + else: + raise + """Get path to the image sequence script.""" try: from pathlib import Path @@ -192,6 +200,38 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): families_transfer = ["render3d", "render2d", "ftrack", "slate"] plugin_python_version = "3.7" + # script path for publish_filesequence.py + publishing_script = None + + def _create_metadata_path(self, instance): + ins_data = instance.data + # Ensure output dir exists + output_dir = ins_data.get("publishRenderFolder", ins_data["outputDir"]) + + try: + if not os.path.isdir(output_dir): + os.makedirs(output_dir) + except OSError: + # directory is not available + self.log.warning("Path is unreachable: `{}`".format(output_dir)) + + metadata_filename = "{}_metadata.json".format(ins_data["subset"]) + + metadata_path = os.path.join(output_dir, metadata_filename) + + # Convert output dir to `{root}/rest/of/path/...` with Anatomy + success, roothless_mtdt_p = self.anatomy.find_root_template_from_path( + metadata_path) + if not success: + # `rootless_path` is not set to `output_dir` if none of roots match + self.log.warning(( + "Could not find root path for remapping \"{}\"." + " This may cause issues on farm." + ).format(output_dir)) + roothless_mtdt_p = metadata_path + + return (metadata_path, roothless_mtdt_p) + def _submit_deadline_post_job(self, instance, job): """Submit publish job to Deadline. @@ -205,17 +245,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): job_name = "Publish - {subset}".format(subset=subset) output_dir = instance.data["outputDir"] - # Convert output dir to `{root}/rest/of/path/...` with Anatomy - success, rootless_path = ( - self.anatomy.find_root_template_from_path(output_dir) - ) - if not success: - # `rootless_path` is not set to `output_dir` if none of roots match - self.log.warning(( - "Could not find root path for remapping \"{}\"." - " This may cause issues on farm." - ).format(output_dir)) - rootless_path = output_dir # Generate the payload for Deadline submission payload = { @@ -239,7 +268,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): }, "PluginInfo": { "Version": self.plugin_python_version, - "ScriptFile": _get_script(), + "ScriptFile": _get_script(self.publishing_script), "Arguments": "", "SingleFrameOnly": "True", }, @@ -249,11 +278,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # Transfer the environment from the original job to this dependent # job so they use the same environment - metadata_filename = "{}_metadata.json".format(subset) - metadata_path = os.path.join(rootless_path, metadata_filename) + metadata_path, roothless_metadata_path = self._create_metadata_path( + instance) environment = job["Props"].get("Env", {}) - environment["PYPE_METADATA_FILE"] = metadata_path + environment["PYPE_METADATA_FILE"] = roothless_metadata_path environment["AVALON_PROJECT"] = io.Session["AVALON_PROJECT"] environment["PYPE_LOG_NO_COLORS"] = "1" try: @@ -854,14 +883,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): } publish_job.update({"ftrack": ftrack}) - # Ensure output dir exists - output_dir = instance.data["outputDir"] - if not os.path.isdir(output_dir): - os.makedirs(output_dir) + metadata_path, roothless_metadata_path = self._create_metadata_path( + instance) - metadata_filename = "{}_metadata.json".format(subset) - - metadata_path = os.path.join(output_dir, metadata_filename) self.log.info("Writing json file: {}".format(metadata_path)) with open(metadata_path, "w") as f: json.dump(publish_job, f, indent=4, sort_keys=True) From 4d638ebdc85ff9077e79b49af2e38ee6fd344c2f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Jul 2020 11:08:04 +0200 Subject: [PATCH 49/58] added item to tray menu with version --- pype/tools/tray/pype_tray.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/pype/tools/tray/pype_tray.py b/pype/tools/tray/pype_tray.py index 7dda8bf4f7..99832f0313 100644 --- a/pype/tools/tray/pype_tray.py +++ b/pype/tools/tray/pype_tray.py @@ -4,6 +4,11 @@ import platform from avalon import style from Qt import QtCore, QtGui, QtWidgets, QtSvg from pype.api import config, Logger, resources +import pype.version +try: + import configparser +except Exception: + import ConfigParser as configparser class TrayManager: @@ -100,6 +105,11 @@ class TrayManager: if items and self.services_submenu is not None: self.add_separator(self.tray_widget.menu) + version_string = self._version_string() + version_action = QtWidgets.QAction(version_string, self.tray_widget) + self.tray_widget.menu.addAction(version_action) + self.add_separator(self.tray_widget.menu) + # Add Exit action to menu aExit = QtWidgets.QAction("&Exit", self.tray_widget) aExit.triggered.connect(self.tray_widget.exit) @@ -109,6 +119,31 @@ class TrayManager: self.connect_modules() self.start_modules() + def _version_string(self): + subversion = None + client_name = None + config_file_path = os.path.join( + os.environ["PYPE_SETUP_PATH"], "pypeapp", "config.ini" + ) + version_string = pype.version.__version__ + if os.path.exists(config_file_path): + config = configparser.ConfigParser() + config.read(config_file_path) + try: + default_config = config["CLIENT"] + except Exception: + default_config = {} + subversion = default_config.get("subversion") + client_name = default_config.get("client_name") + + if subversion: + version_string += " ({})".format(subversion) + + if client_name: + version_string += ", {}".format(client_name) + + return version_string + def process_items(self, items, parent_menu): """ Loop through items and add them to parent_menu. From f4d49b330eabd9d9f6d5a744f1b9dd7374080df7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Jul 2020 11:12:47 +0200 Subject: [PATCH 50/58] add version item only if client information is in config.ini --- pype/tools/tray/pype_tray.py | 40 ++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/pype/tools/tray/pype_tray.py b/pype/tools/tray/pype_tray.py index 99832f0313..3d6d6d473b 100644 --- a/pype/tools/tray/pype_tray.py +++ b/pype/tools/tray/pype_tray.py @@ -105,10 +105,7 @@ class TrayManager: if items and self.services_submenu is not None: self.add_separator(self.tray_widget.menu) - version_string = self._version_string() - version_action = QtWidgets.QAction(version_string, self.tray_widget) - self.tray_widget.menu.addAction(version_action) - self.add_separator(self.tray_widget.menu) + self._add_version_item() # Add Exit action to menu aExit = QtWidgets.QAction("&Exit", self.tray_widget) @@ -119,30 +116,37 @@ class TrayManager: self.connect_modules() self.start_modules() - def _version_string(self): - subversion = None - client_name = None + def _add_version_item(self): config_file_path = os.path.join( os.environ["PYPE_SETUP_PATH"], "pypeapp", "config.ini" ) - version_string = pype.version.__version__ - if os.path.exists(config_file_path): - config = configparser.ConfigParser() - config.read(config_file_path) - try: - default_config = config["CLIENT"] - except Exception: - default_config = {} - subversion = default_config.get("subversion") - client_name = default_config.get("client_name") + if not os.path.exists(config_file_path): + return + subversion = None + client_name = None + + config = configparser.ConfigParser() + config.read(config_file_path) + try: + default_config = config["CLIENT"] + except Exception: + default_config = {} + subversion = default_config.get("subversion") + client_name = default_config.get("client_name") + if not subversion and not client_name: + return + + version_string = pype.version.__version__ if subversion: version_string += " ({})".format(subversion) if client_name: version_string += ", {}".format(client_name) - return version_string + version_action = QtWidgets.QAction(version_string, self.tray_widget) + self.tray_widget.menu.addAction(version_action) + self.add_separator(self.tray_widget.menu) def process_items(self, items, parent_menu): """ Loop through items and add them to parent_menu. From 60501b17632297ddd77d07c437c31cfc59da03ae Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Jul 2020 12:30:28 +0200 Subject: [PATCH 51/58] added time check for limit of 10 request in 1 second --- pype/modules/clockify/clockify_api.py | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pype/modules/clockify/clockify_api.py b/pype/modules/clockify/clockify_api.py index 0a09c65628..d88b2ef8df 100644 --- a/pype/modules/clockify/clockify_api.py +++ b/pype/modules/clockify/clockify_api.py @@ -1,5 +1,6 @@ import os import re +import time import requests import json import datetime @@ -8,12 +9,27 @@ from .constants import ( ) +def time_check(obj): + if obj.request_counter < 10: + obj.request_counter += 1 + return + + wait_time = 1 - (time.time() - obj.request_time) + if wait_time > 0: + time.sleep(wait_time) + + obj.request_time = time.time() + obj.request_counter = 0 + + class ClockifyAPI: def __init__(self, api_key=None, master_parent=None): self.workspace_name = None self.workspace_id = None self.master_parent = master_parent self.api_key = api_key + self.request_counter = 0 + self.request_time = time.time() @property def headers(self): @@ -40,6 +56,7 @@ class ClockifyAPI: def validate_api_key(self, api_key): test_headers = {'X-Api-Key': api_key} action_url = 'workspaces/' + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=test_headers @@ -57,6 +74,7 @@ class ClockifyAPI: action_url = "/workspaces/{}/users/{}/permissions".format( workspace_id, user_id ) + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -69,6 +87,7 @@ class ClockifyAPI: def get_user_id(self): action_url = 'v1/user/' + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -129,6 +148,7 @@ class ClockifyAPI: def get_workspaces(self): action_url = 'workspaces/' + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -141,6 +161,7 @@ class ClockifyAPI: if workspace_id is None: workspace_id = self.workspace_id action_url = 'workspaces/{}/projects/'.format(workspace_id) + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -156,6 +177,7 @@ class ClockifyAPI: action_url = 'workspaces/{}/projects/{}/'.format( workspace_id, project_id ) + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -167,6 +189,7 @@ class ClockifyAPI: if workspace_id is None: workspace_id = self.workspace_id action_url = 'workspaces/{}/tags/'.format(workspace_id) + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -182,6 +205,7 @@ class ClockifyAPI: action_url = 'workspaces/{}/projects/{}/tasks/'.format( workspace_id, project_id ) + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -264,6 +288,7 @@ class ClockifyAPI: "taskId": task_id, "tagIds": tag_ids } + time_check(self) response = requests.post( CLOCKIFY_ENDPOINT + action_url, headers=self.headers, @@ -281,6 +306,7 @@ class ClockifyAPI: action_url = 'workspaces/{}/timeEntries/inProgress'.format( workspace_id ) + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -311,6 +337,7 @@ class ClockifyAPI: "tagIds": current["tagIds"], "end": self.get_current_time() } + time_check(self) response = requests.put( CLOCKIFY_ENDPOINT + action_url, headers=self.headers, @@ -324,6 +351,7 @@ class ClockifyAPI: if workspace_id is None: workspace_id = self.workspace_id action_url = 'workspaces/{}/timeEntries/'.format(workspace_id) + time_check(self) response = requests.get( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -336,6 +364,7 @@ class ClockifyAPI: action_url = 'workspaces/{}/timeEntries/{}'.format( workspace_id, tid ) + time_check(self) response = requests.delete( CLOCKIFY_ENDPOINT + action_url, headers=self.headers @@ -357,6 +386,7 @@ class ClockifyAPI: "color": "#f44336", "billable": "true" } + time_check(self) response = requests.post( CLOCKIFY_ENDPOINT + action_url, headers=self.headers, @@ -367,6 +397,7 @@ class ClockifyAPI: def add_workspace(self, name): action_url = 'workspaces/' body = {"name": name} + time_check(self) response = requests.post( CLOCKIFY_ENDPOINT + action_url, headers=self.headers, @@ -386,6 +417,7 @@ class ClockifyAPI: "name": name, "projectId": project_id } + time_check(self) response = requests.post( CLOCKIFY_ENDPOINT + action_url, headers=self.headers, @@ -400,6 +432,7 @@ class ClockifyAPI: body = { "name": name } + time_check(self) response = requests.post( CLOCKIFY_ENDPOINT + action_url, headers=self.headers, @@ -415,6 +448,7 @@ class ClockifyAPI: action_url = '/workspaces/{}/projects/{}'.format( workspace_id, project_id ) + time_check(self) response = requests.delete( CLOCKIFY_ENDPOINT + action_url, headers=self.headers, From 50422e3b057e4de54e56528497b8b1d671c87f48 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 24 Jul 2020 17:26:41 +0200 Subject: [PATCH 52/58] reset matching profiles if profile value is higher than previous --- pype/plugins/global/publish/integrate_new.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index d151cfc608..6cb38c282c 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -740,6 +740,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): value += 1 if value > highest_value: + matching_profiles = {} highest_value = value if value == highest_value: From b7cf6679248a169f8bff0323a48014cb0e139433 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 10:03:22 +0200 Subject: [PATCH 53/58] show the version string all the time --- pype/tools/tray/pype_tray.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/pype/tools/tray/pype_tray.py b/pype/tools/tray/pype_tray.py index 3d6d6d473b..9537b62581 100644 --- a/pype/tools/tray/pype_tray.py +++ b/pype/tools/tray/pype_tray.py @@ -120,22 +120,18 @@ class TrayManager: config_file_path = os.path.join( os.environ["PYPE_SETUP_PATH"], "pypeapp", "config.ini" ) - if not os.path.exists(config_file_path): - return - subversion = None - client_name = None + default_config = {} + if os.path.exists(config_file_path): + config = configparser.ConfigParser() + config.read(config_file_path) + try: + default_config = config["CLIENT"] + except Exception: + pass - config = configparser.ConfigParser() - config.read(config_file_path) - try: - default_config = config["CLIENT"] - except Exception: - default_config = {} subversion = default_config.get("subversion") client_name = default_config.get("client_name") - if not subversion and not client_name: - return version_string = pype.version.__version__ if subversion: From f52a1de2d1ccb803b929c7b2e9fac5b1806a565e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 12:25:38 +0200 Subject: [PATCH 54/58] fix stored project entity in collect ftrack api --- pype/plugins/ftrack/publish/collect_ftrack_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/ftrack/publish/collect_ftrack_api.py b/pype/plugins/ftrack/publish/collect_ftrack_api.py index 151b8882a3..bbda6da3b0 100644 --- a/pype/plugins/ftrack/publish/collect_ftrack_api.py +++ b/pype/plugins/ftrack/publish/collect_ftrack_api.py @@ -96,6 +96,6 @@ class CollectFtrackApi(pyblish.api.ContextPlugin): task_entity = None self.log.warning("Task name is not set.") - context.data["ftrackProject"] = asset_entity + context.data["ftrackProject"] = project_entity context.data["ftrackEntity"] = asset_entity context.data["ftrackTask"] = task_entity From 6f2132b0aa94d412ef37a0e28b6abaca217d2fdf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 12:26:10 +0200 Subject: [PATCH 55/58] added function _asset_version_status for getting statuses --- .../plugins/ftrack/publish/integrate_ftrack_api.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_api.py b/pype/plugins/ftrack/publish/integrate_ftrack_api.py index cd94b2a150..59740529bb 100644 --- a/pype/plugins/ftrack/publish/integrate_ftrack_api.py +++ b/pype/plugins/ftrack/publish/integrate_ftrack_api.py @@ -54,8 +54,20 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): self.log.debug(query) return query - def process(self, instance): + def _asset_version_status(self, instance, session): + status_name = instance.context.data.get("ftrackStatus") + if not status_name: + return None + project_entity = instance.context.data.get("ftrackProject") + project_schema = project_entity["project_schema"] + asset_version_statuses = project_schema.get_statuses("AssetVersion") + asset_version_statuses_by_low_name = { + status["name"].lower(): status for status in asset_version_statuses + } + return asset_version_statuses_by_low_name.get(status_name.lower()) + + def process(self, instance): session = instance.context.data["ftrackSession"] if instance.data.get("ftrackTask"): task = instance.data["ftrackTask"] From 94be335e91f3a7c154db5c4860d35d7c37cb1508 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 12:27:37 +0200 Subject: [PATCH 56/58] change staus of asset version if is set --- .../ftrack/publish/integrate_ftrack_api.py | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_api.py b/pype/plugins/ftrack/publish/integrate_ftrack_api.py index 59740529bb..e5f62a93ea 100644 --- a/pype/plugins/ftrack/publish/integrate_ftrack_api.py +++ b/pype/plugins/ftrack/publish/integrate_ftrack_api.py @@ -90,9 +90,11 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): info_msg += ", metadata: {metadata}." used_asset_versions = [] + + asset_version_status = self._asset_version_status(instance, session) + # Iterate over components and publish for data in instance.data.get("ftrackComponentsList", []): - # AssetType # Get existing entity. assettype_data = {"short": "upload"} @@ -106,9 +108,9 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): # Create a new entity if none exits. if not assettype_entity: assettype_entity = session.create("AssetType", assettype_data) - self.log.debug( - "Created new AssetType with data: ".format(assettype_data) - ) + self.log.debug("Created new AssetType with data: {}".format( + assettype_data + )) # Asset # Get existing entity. @@ -215,6 +217,17 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): assetversion_entity["id"], str(asset_version_comment) )) + if asset_version_status: + assetversion_entity["status"] = asset_version_status + try: + session.commit() + except Exception: + session.rollback() + status_name = instance.context.data["ftrackStatus"] + self.log.warning(( + "Couldn't set status \"{0}\" to AssetVersion \"{1}\"." + ).format(status_name, assetversion_entity["id"])) + # Adding Custom Attributes for attr, val in assetversion_cust_attrs.items(): if attr in assetversion_entity["custom_attributes"]: From d14975d9b1899594d0e7553974f385f80524f010 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 12:46:25 +0200 Subject: [PATCH 57/58] change status on task not on asset version --- .../ftrack/publish/integrate_ftrack_api.py | 59 +++++++++++++------ 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_api.py b/pype/plugins/ftrack/publish/integrate_ftrack_api.py index e5f62a93ea..0c4c6d49b5 100644 --- a/pype/plugins/ftrack/publish/integrate_ftrack_api.py +++ b/pype/plugins/ftrack/publish/integrate_ftrack_api.py @@ -54,18 +54,50 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): self.log.debug(query) return query - def _asset_version_status(self, instance, session): + def _set_task_status(self, instance, task_entity, session): + project_entity = instance.context.data.get("ftrackProject") + if not project_entity: + self.log.info("Task status won't be set, project is not known.") + return + + if not task_entity: + self.log.info("Task status won't be set, task is not known.") + return + status_name = instance.context.data.get("ftrackStatus") if not status_name: - return None + self.log.info("Ftrack status name is not set.") + return + + self.log.debug( + "Ftrack status name will be (maybe) set to \"{}\"".format( + status_name + ) + ) - project_entity = instance.context.data.get("ftrackProject") project_schema = project_entity["project_schema"] - asset_version_statuses = project_schema.get_statuses("AssetVersion") - asset_version_statuses_by_low_name = { - status["name"].lower(): status for status in asset_version_statuses + task_statuses = project_schema.get_statuses( + "Task", task_entity["type_id"] + ) + task_statuses_by_low_name = { + status["name"].lower(): status for status in task_statuses } - return asset_version_statuses_by_low_name.get(status_name.lower()) + status = task_statuses_by_low_name.get(status_name.lower()) + if not status: + self.log.warning(( + "Task status \"{}\" won't be set," + " status is now allowed on task type \"{}\"." + ).format(status_name, task_entity["type"]["name"])) + return + + self.log.info("Setting task status to \"{}\"".format(status_name)) + task_entity["status"] = status + try: + session.commit() + except Exception: + tp, value, tb = sys.exc_info() + session.rollback() + six.reraise(tp, value, tb) def process(self, instance): session = instance.context.data["ftrackSession"] @@ -91,7 +123,7 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): used_asset_versions = [] - asset_version_status = self._asset_version_status(instance, session) + self._set_task_status(instance, task, session) # Iterate over components and publish for data in instance.data.get("ftrackComponentsList", []): @@ -217,17 +249,6 @@ class IntegrateFtrackApi(pyblish.api.InstancePlugin): assetversion_entity["id"], str(asset_version_comment) )) - if asset_version_status: - assetversion_entity["status"] = asset_version_status - try: - session.commit() - except Exception: - session.rollback() - status_name = instance.context.data["ftrackStatus"] - self.log.warning(( - "Couldn't set status \"{0}\" to AssetVersion \"{1}\"." - ).format(status_name, assetversion_entity["id"])) - # Adding Custom Attributes for attr, val in assetversion_cust_attrs.items(): if attr in assetversion_entity["custom_attributes"]: From eec1d35e3f60440e3386bdcbbb66f7aa6a468ba0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 27 Jul 2020 13:04:58 +0200 Subject: [PATCH 58/58] skip scene for changing version to taks statuses --- pype/modules/ftrack/events/event_version_to_task_statuses.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pype/modules/ftrack/events/event_version_to_task_statuses.py b/pype/modules/ftrack/events/event_version_to_task_statuses.py index 3ff986f9c6..fdb48cbc37 100644 --- a/pype/modules/ftrack/events/event_version_to_task_statuses.py +++ b/pype/modules/ftrack/events/event_version_to_task_statuses.py @@ -84,6 +84,9 @@ class VersionToTaskStatus(BaseEvent): if not task: continue + if version["asset"]["type"]["short"].lower() == "scene": + continue + project_schema = task["project"]["project_schema"] # Get all available statuses for Task statuses = project_schema.get_statuses("Task", task["type_id"])