Repository: ynput/OpenPype
Issue: Ftrack actions in job monitor

 Ftrack action Sync to Avalon is not visible in Ftrack Job monitor when in progress. 

[cuID:PYPE-1198]

Changed files:
openpype/modules/default_modules/ftrack/event_handlers_user/action_sync_to_avalon.py
openpype/modules/default_modules/ftrack/lib/ftrack_base_handler.py
openpype/modules/default_modules/ftrack/event_handlers_server/action_sync_to_avalon.py

diff --git a/openpype/modules/default_modules/ftrack/event_handlers_server/action_sync_to_avalon.py b/openpype/modules/default_modules/ftrack/event_handlers_server/action_sync_to_avalon.py
index d449c4b7df..58f79e8a2b 100644
--- a/openpype/modules/default_modules/ftrack/event_handlers_server/action_sync_to_avalon.py
+++ b/openpype/modules/default_modules/ftrack/event_handlers_server/action_sync_to_avalon.py
@@ -1,4 +1,6 @@
 import time
+import sys
+import json
 import traceback
 
 from openpype_modules.ftrack.lib import ServerAction
@@ -52,17 +54,80 @@ class SyncToAvalonServer(ServerAction):
         return False
 
     def launch(self, session, in_entities, event):
+        self.log.debug("{}: Creating job".format(self.label))
+
+        user_entity = session.query(
+            "User where id is {}".format(event["source"]["user"]["id"])
+        ).one()
+        job_entity = session.create("Job", {
+            "user": user_entity,
+            "status": "running",
+            "data": json.dumps({
+                "description": "Sync to avalon is running..."
+            })
+        })
+        session.commit()
+
+        project_entity = self.get_project_from_entity(in_entities[0])
+        project_name = project_entity["full_name"]
+
+        try:
+            result = self.synchronization(event, project_name)
+
+        except Exception:
+            self.log.error(
+                "Synchronization failed due to code error", exc_info=True
+            )
+
+            description = "Sync to avalon Crashed (Download traceback)"
+            self.add_traceback_to_job(
+                job_entity, session, sys.exc_info(), description
+            )
+
+            msg = "An error has happened during synchronization"
+            title = "Synchronization report ({}):".format(project_name)
+            items = []
+            items.append({
+                "type": "label",
+                "value": "# {}".format(msg)
+            })
+            items.append({
+                "type": "label",
+                "value": (
+                    "<p>Download report from job for more information.</p>"
+                )
+            })
+
+            report = {}
+            try:
+                report = self.entities_factory.report()
+            except Exception:
+                pass
+
+            _items = report.get("items") or []
+            if _items:
+                items.append(self.entities_factory.report_splitter)
+                items.extend(_items)
+
+            self.show_interface(items, title, event, submit_btn_label="Ok")
+
+            return {"success": True, "message": msg}
+
+        job_entity["status"] = "done"
+        job_entity["data"] = json.dumps({
+            "description": "Sync to avalon finished."
+        })
+        session.commit()
+
+        return result
+
+    def synchronization(self, event, project_name):
         time_start = time.time()
 
         self.show_message(event, "Synchronization - Preparing data", True)
-        # Get ftrack project
-        if in_entities[0].entity_type.lower() == "project":
-            ft_project_name = in_entities[0]["full_name"]
-        else:
-            ft_project_name = in_entities[0]["project"]["full_name"]
 
         try:
-            output = self.entities_factory.launch_setup(ft_project_name)
+            output = self.entities_factory.launch_setup(project_name)
             if output is not None:
                 return output
 
@@ -72,7 +137,7 @@ class SyncToAvalonServer(ServerAction):
             time_2 = time.time()
 
             # This must happen before all filtering!!!
-            self.entities_factory.prepare_avalon_entities(ft_project_name)
+            self.entities_factory.prepare_avalon_entities(project_name)
             time_3 = time.time()
 
             self.entities_factory.filter_by_ignore_sync()
@@ -118,7 +183,7 @@ class SyncToAvalonServer(ServerAction):
             report = self.entities_factory.report()
             if report and report.get("items"):
                 default_title = "Synchronization report ({}):".format(
-                    ft_project_name
+                    project_name
                 )
                 self.show_interface(
                     items=report["items"],
@@ -130,46 +195,6 @@ class SyncToAvalonServer(ServerAction):
                 "message": "Synchronization Finished"
             }
 
-        except Exception:
-            self.log.error(
-                "Synchronization failed due to code error", exc_info=True
-            )
-            msg = "An error has happened during synchronization"
-            title = "Synchronization report ({}):".format(ft_project_name)
-            items = []
-            items.append({
-                "type": "label",
-                "value": "# {}".format(msg)
-            })
-            items.append({
-                "type": "label",
-                "value": "## Traceback of the error"
-            })
-            items.append({
-                "type": "label",
-                "value": "<p>{}</p>".format(
-                    str(traceback.format_exc()).replace(
-                        "\\n", "<br>").replace(
-                        " ", "&nbsp;"
-                    )
-                )
-            })
-
-            report = {"items": []}
-            try:
-                report = self.entities_factory.report()
-            except Exception:
-                pass
-
-            _items = report.get("items", [])
-            if _items:
-                items.append(self.entities_factory.report_splitter)
-                items.extend(_items)
-
-            self.show_interface(items, title, event)
-
-            return {"success": True, "message": msg}
-
         finally:
             try:
                 self.entities_factory.dbcon.uninstall()
diff --git a/openpype/modules/default_modules/ftrack/event_handlers_user/action_sync_to_avalon.py b/openpype/modules/default_modules/ftrack/event_handlers_user/action_sync_to_avalon.py
index d6ca561bbe..cd2f371f38 100644
--- a/openpype/modules/default_modules/ftrack/event_handlers_user/action_sync_to_avalon.py
+++ b/openpype/modules/default_modules/ftrack/event_handlers_user/action_sync_to_avalon.py
@@ -1,4 +1,6 @@
 import time
+import sys
+import json
 import traceback
 
 from openpype_modules.ftrack.lib import BaseAction, statics_icon
@@ -30,17 +32,10 @@ class SyncToAvalonLocal(BaseAction):
         - or do it manually (Not recommended)
     """
 
-    #: Action identifier.
     identifier = "sync.to.avalon.local"
-    #: Action label.
     label = "OpenPype Admin"
-    #: Action variant
     variant = "- Sync To Avalon (Local)"
-    #: Action description.
-    description = "Send data from Ftrack to Avalon"
-    #: priority
     priority = 200
-    #: roles that are allowed to register this action
     icon = statics_icon("ftrack", "action_icons", "OpenPypeAdmin.svg")
 
     settings_key = "sync_to_avalon_local"
@@ -63,17 +58,80 @@ class SyncToAvalonLocal(BaseAction):
         return is_valid
 
     def launch(self, session, in_entities, event):
+        self.log.debug("{}: Creating job".format(self.label))
+
+        user_entity = session.query(
+            "User where id is {}".format(event["source"]["user"]["id"])
+        ).one()
+        job_entity = session.create("Job", {
+            "user": user_entity,
+            "status": "running",
+            "data": json.dumps({
+                "description": "Sync to avalon is running..."
+            })
+        })
+        session.commit()
+
+        project_entity = self.get_project_from_entity(in_entities[0])
+        project_name = project_entity["full_name"]
+
+        try:
+            result = self.synchronization(event, project_name)
+
+        except Exception:
+            self.log.error(
+                "Synchronization failed due to code error", exc_info=True
+            )
+
+            description = "Sync to avalon Crashed (Download traceback)"
+            self.add_traceback_to_job(
+                job_entity, session, sys.exc_info(), description
+            )
+
+            msg = "An error has happened during synchronization"
+            title = "Synchronization report ({}):".format(project_name)
+            items = []
+            items.append({
+                "type": "label",
+                "value": "# {}".format(msg)
+            })
+            items.append({
+                "type": "label",
+                "value": (
+                    "<p>Download report from job for more information.</p>"
+                )
+            })
+
+            report = {}
+            try:
+                report = self.entities_factory.report()
+            except Exception:
+                pass
+
+            _items = report.get("items") or []
+            if _items:
+                items.append(self.entities_factory.report_splitter)
+                items.extend(_items)
+
+            self.show_interface(items, title, event, submit_btn_label="Ok")
+
+            return {"success": True, "message": msg}
+
+        job_entity["status"] = "done"
+        job_entity["data"] = json.dumps({
+            "description": "Sync to avalon finished."
+        })
+        session.commit()
+
+        return result
+
+    def synchronization(self, event, project_name):
         time_start = time.time()
 
         self.show_message(event, "Synchronization - Preparing data", True)
-        # Get ftrack project
-        if in_entities[0].entity_type.lower() == "project":
-            ft_project_name = in_entities[0]["full_name"]
-        else:
-            ft_project_name = in_entities[0]["project"]["full_name"]
 
         try:
-            output = self.entities_factory.launch_setup(ft_project_name)
+            output = self.entities_factory.launch_setup(project_name)
             if output is not None:
                 return output
 
@@ -83,7 +141,7 @@ class SyncToAvalonLocal(BaseAction):
             time_2 = time.time()
 
             # This must happen before all filtering!!!
-            self.entities_factory.prepare_avalon_entities(ft_project_name)
+            self.entities_factory.prepare_avalon_entities(project_name)
             time_3 = time.time()
 
             self.entities_factory.filter_by_ignore_sync()
@@ -129,7 +187,7 @@ class SyncToAvalonLocal(BaseAction):
             report = self.entities_factory.report()
             if report and report.get("items"):
                 default_title = "Synchronization report ({}):".format(
-                    ft_project_name
+                    project_name
                 )
                 self.show_interface(
                     items=report["items"],
@@ -141,46 +199,6 @@ class SyncToAvalonLocal(BaseAction):
                 "message": "Synchronization Finished"
             }
 
-        except Exception:
-            self.log.error(
-                "Synchronization failed due to code error", exc_info=True
-            )
-            msg = "An error occurred during synchronization"
-            title = "Synchronization report ({}):".format(ft_project_name)
-            items = []
-            items.append({
-                "type": "label",
-                "value": "# {}".format(msg)
-            })
-            items.append({
-                "type": "label",
-                "value": "## Traceback of the error"
-            })
-            items.append({
-                "type": "label",
-                "value": "<p>{}</p>".format(
-                    str(traceback.format_exc()).replace(
-                        "\\n", "<br>").replace(
-                        " ", "&nbsp;"
-                    )
-                )
-            })
-
-            report = {"items": []}
-            try:
-                report = self.entities_factory.report()
-            except Exception:
-                pass
-
-            _items = report.get("items", [])
-            if _items:
-                items.append(self.entities_factory.report_splitter)
-                items.extend(_items)
-
-            self.show_interface(items, title, event)
-
-            return {"success": True, "message": msg}
-
         finally:
             try:
                 self.entities_factory.dbcon.uninstall()
diff --git a/openpype/modules/default_modules/ftrack/lib/ftrack_base_handler.py b/openpype/modules/default_modules/ftrack/lib/ftrack_base_handler.py
index 7027154d86..a457b886ac 100644
--- a/openpype/modules/default_modules/ftrack/lib/ftrack_base_handler.py
+++ b/openpype/modules/default_modules/ftrack/lib/ftrack_base_handler.py
@@ -384,8 +384,8 @@ class BaseHandler(object):
         )
 
     def show_interface(
-        self, items, title='',
-        event=None, user=None, username=None, user_id=None
+        self, items, title="", event=None, user=None,
+        username=None, user_id=None, submit_btn_label=None
     ):
         """
         Shows interface to user
@@ -428,14 +428,18 @@ class BaseHandler(object):
             'applicationId=ftrack.client.web and user.id="{0}"'
         ).format(user_id)
 
+        event_data = {
+            "type": "widget",
+            "items": items,
+            "title": title
+        }
+        if submit_btn_label:
+            event_data["submit_button_label"] = submit_btn_label
+
         self.session.event_hub.publish(
             ftrack_api.event.base.Event(
                 topic='ftrack.action.trigger-user-interface',
-                data=dict(
-                    type='widget',
-                    items=items,
-                    title=title
-                ),
+                data=event_data,
                 target=target
             ),
             on_error='ignore'
@@ -443,7 +447,7 @@ class BaseHandler(object):
 
     def show_interface_from_dict(
         self, messages, title="", event=None,
-        user=None, username=None, user_id=None
+        user=None, username=None, user_id=None, submit_btn_label=None
     ):
         if not messages:
             self.log.debug("No messages to show! (messages dict is empty)")
@@ -469,7 +473,9 @@ class BaseHandler(object):
                 message = {'type': 'label', 'value': '<p>{}</p>'.format(value)}
                 items.append(message)
 
-        self.show_interface(items, title, event, user, username, user_id)
+        self.show_interface(
+            items, title, event, user, username, user_id, submit_btn_label
+        )
 
     def trigger_action(
         self, action_name, event=None, session=None,