Merged in feature/2.0/PYPE-306_update_enchancement (pull request #119)

Feature/2.0/PYPE-306 update enchancement

Approved-by: Milan Kolar <milan@orbi.tools>
This commit is contained in:
Jakub Trllo 2019-04-26 12:49:42 +00:00 committed by Milan Kolar
commit 416bd2860d
20 changed files with 230 additions and 235 deletions

View file

@ -35,6 +35,28 @@ class ClockifyModule:
self.set_menu_visibility()
def process_modules(self, modules):
if 'FtrackModule' in modules:
actions_path = os.path.sep.join([
os.path.dirname(__file__),
'ftrack_actions'
])
current = os.environ('FTRACK_ACTIONS_PATH', '')
if current:
current += os.pathsep
os.environ['FTRACK_ACTIONS_PATH'] = current + actions_path
if 'AvalonApps' in modules:
from launcher import lib
actions_path = os.path.sep.join([
os.path.dirname(__file__),
'launcher_actions'
])
current = os.environ.get('AVALON_ACTIONS', '')
if current:
current += os.pathsep
os.environ['AVALON_ACTIONS'] = current + actions_path
def start_timer_check(self):
self.bool_thread_check_running = True
if self.thread_timer_check is None:

View file

@ -1,3 +1,4 @@
import os
import sys
import argparse
import logging
@ -17,7 +18,9 @@ class StartClockify(BaseAction):
#: Action description.
description = 'Starts timer on clockify'
#: roles that are allowed to register this action
icon = 'https://clockify.me/assets/images/clockify-logo.png'
icon = '{}/app_icons/clockify.png'.format(
os.environ.get('PYPE_STATICS_SERVER', '')
)
#: Clockify api
clockapi = ClockifyAPI()

View file

@ -1,3 +1,4 @@
import os
import sys
import argparse
import logging
@ -21,7 +22,9 @@ class SyncClocify(BaseAction):
#: roles that are allowed to register this action
role_list = ['Pypeclub', 'Administrator']
#: icon
icon = 'https://clockify.me/assets/images/clockify-logo-white.svg'
icon = '{}/app_icons/clockify-white.png'.format(
os.environ.get('PYPE_STATICS_SERVER', '')
)
#: CLockifyApi
clockapi = ClockifyAPI()

View file

@ -1,9 +1,7 @@
from avalon import api, io
from pype.api import Logger
try:
from pype.clockify import ClockifyAPI
except Exception:
pass
from pype.clockify import ClockifyAPI
log = Logger().get_logger(__name__, "clockify_start")
@ -14,13 +12,10 @@ class ClockifyStart(api.Action):
label = "Clockify - Start Timer"
icon = "clockify_icon"
order = 500
exec("try: clockapi = ClockifyAPI()\nexcept: clockapi = None")
clockapi = ClockifyAPI()
def is_compatible(self, session):
"""Return whether the action is compatible with the session"""
if self.clockapi is None:
return False
if "AVALON_TASK" in session:
return True
return False

View file

@ -1,8 +1,5 @@
from avalon import api, io
try:
from pype.clockify import ClockifyAPI
except Exception:
pass
from pype.clockify import ClockifyAPI
from pype.api import Logger
log = Logger().get_logger(__name__, "clockify_sync")
@ -13,16 +10,11 @@ class ClockifySync(api.Action):
label = "Sync to Clockify"
icon = "clockify_white_icon"
order = 500
exec(
"try:\n\tclockapi = ClockifyAPI()"
"\n\thave_permissions = clockapi.validate_workspace_perm()"
"\nexcept:\n\tclockapi = None"
)
clockapi = ClockifyAPI()
have_permissions = clockapi.validate_workspace_perm()
def is_compatible(self, session):
"""Return whether the action is compatible with the session"""
if self.clockapi is None:
return False
return self.have_permissions
def process(self, session, **kwargs):

View file

@ -35,10 +35,12 @@ def registerApp(app, session):
label = apptoml.get('ftrack_label', app.get('label', name))
icon = apptoml.get('ftrack_icon', None)
description = apptoml.get('description', None)
preactions = apptoml.get('preactions', [])
# register action
AppAction(
session, label, name, executable, variant, icon, description
session, label, name, executable, variant,
icon, description, preactions
).register()

View file

@ -3,11 +3,9 @@ import sys
import logging
import argparse
import re
# import json
from pype.vendor import ftrack_api
from pype.ftrack import BaseAction
# from pype import api as pype, lib as pypelib
from avalon import lib as avalonlib
from avalon.tools.libraryloader.io_nonsingleton import DbConnector
from pypeapp import config, Anatomy
@ -239,17 +237,6 @@ class CreateFolders(BaseAction):
output.extend(self.get_notask_children(child))
return output
# def get_presets(self):
# fpath_items = [pypelib.get_presets_path(), 'tools', 'sw_folders.json']
# filepath = os.path.normpath(os.path.sep.join(fpath_items))
# presets = dict()
# try:
# with open(filepath) as data_file:
# presets = json.load(data_file)
# except Exception as e:
# self.log.warning('Wasn\'t able to load presets')
# return dict(presets)
def template_format(self, template, data):
partial_data = PartialDict(data)

View file

@ -42,7 +42,7 @@ class CreateProjectFolders(BaseAction):
else:
project = entity['project']
presets = config.load_presets()['tools']['project_folder_structure']
presets = config.get_presets()['tools']['project_folder_structure']
try:
# Get paths based on presets
basic_paths = self.get_path_items(presets)
@ -142,28 +142,6 @@ class CreateProjectFolders(BaseAction):
self.session.commit()
return new_ent
# def load_presets(self):
# preset_items = [
# pypelib.get_presets_path(),
# 'tools',
# 'project_folder_structure.json'
# ]
# filepath = os.path.sep.join(preset_items)
#
# # Load folder structure template from presets
# presets = dict()
# try:
# with open(filepath) as data_file:
# presets = json.load(data_file)
# except Exception as e:
# msg = 'Unable to load Folder structure preset'
# self.log.warning(msg)
# return {
# 'success': False,
# 'message': msg
# }
# return presets
def get_path_items(self, in_dict):
output = []
for key, value in in_dict.items():

View file

@ -23,9 +23,7 @@ class DJVViewAction(BaseAction):
'''Expects a ftrack_api.Session instance'''
super().__init__(session)
self.djv_path = None
self.config_data = None
# self.load_config_data()
self.config_data = config.get_presets()['djv_view']['config']
self.set_djv_path()
@ -54,22 +52,6 @@ class DJVViewAction(BaseAction):
return True
return False
def load_config_data(self):
# path_items = [pypelib.get_presets_path(), 'djv_view', 'config.json']
path_items = config.get_presets()['djv_view']['config']
filepath = os.path.sep.join(path_items)
data = dict()
try:
with open(filepath) as data_file:
data = json.load(data_file)
except Exception as e:
log.warning(
'Failed to load data from DJV presets file ({})'.format(e)
)
self.config_data = data
def set_djv_path(self):
for path in self.config_data.get("djv_paths", []):
if os.path.exists(path):

View file

@ -1,6 +1,7 @@
import sys
import argparse
import logging
import json
from pype.vendor import ftrack_api
from pype.ftrack import BaseAction
@ -37,14 +38,18 @@ class JobKiller(BaseAction):
).all()
items = []
import json
item_splitter = {'type': 'label', 'value': '---'}
for job in jobs:
data = json.loads(job['data'])
try:
data = json.loads(job['data'])
desctiption = data['description']
except Exception:
desctiption = '*No description*'
user = job['user']['username']
created = job['created_at'].strftime('%d.%m.%Y %H:%M:%S')
label = '{} - {} - {}'.format(
data['description'], created, user
desctiption, created, user
)
item_label = {
'type': 'label',

View file

@ -39,7 +39,6 @@ class RVAction(BaseAction):
)
else:
# if not, fallback to config file location
# self.load_config_data()
self.config_data = config.get_presets()['djv_view']['config']
self.set_rv_path()
@ -61,21 +60,6 @@ class RVAction(BaseAction):
return True
return False
def load_config_data(self):
path_items = config.get_presets['rv']['config.json']
filepath = os.path.sep.join(path_items)
data = dict()
try:
with open(filepath) as data_file:
data = json.load(data_file)
except Exception as e:
log.warning(
'Failed to load data from RV presets file ({})'.format(e)
)
self.config_data = data
def set_rv_path(self):
self.rv_path = self.config_data.get("rv_path")

View file

@ -0,0 +1,79 @@
from pype.vendor import ftrack_api
from pype.ftrack import BaseAction
class StartTimer(BaseAction):
'''Starts timer.'''
identifier = 'start.timer'
label = 'Start timer'
description = 'Starts timer'
def discover(self, session, entities, event):
return False
def _handle_result(*arg):
return
def launch(self, session, entities, event):
entity = entities[0]
if entity.entity_type.lower() != 'task':
return
self.start_ftrack_timer(entity)
try:
self.start_clockify_timer(entity)
except Exception:
self.log.warning(
'Failed starting Clockify timer for task: ' + entity['name']
)
return
def start_ftrack_timer(self, task):
user_query = 'User where username is "{}"'.format(self.session.api_user)
user = self.session.query(user_query).one()
self.log.info('Starting Ftrack timer for task: ' + task['name'])
user.start_timer(task, force=True)
self.session.commit()
def start_clockify_timer(self, task):
# Validate Clockify settings if Clockify is required
clockify_timer = os.environ.get('CLOCKIFY_WORKSPACE', None)
if clockify_timer is None:
return
from pype.clockify import ClockifyAPI
clockapi = ClockifyAPI()
if clockapi.verify_api() is False:
return
task_type = task['type']['name']
project_name = task['project']['full_name']
def get_parents(entity):
output = []
if entity.entity_type.lower() == 'project':
return output
output.extend(get_parents(entity['parent']))
output.append(entity['name'])
return output
desc_items = get_parents(task['parent'])
desc_items.append(task['name'])
description = '/'.join(desc_items)
project_id = clockapi.get_project_id(project_name)
tag_ids = []
tag_ids.append(clockapi.get_tag_id(task_type))
clockapi.start_time_entry(
description, project_id, tag_ids=tag_ids
)
self.log.info('Starting Clockify timer for task: ' + task['name'])
def register(session, **kw):
'''Register plugin. Called when used as an plugin.'''
if not isinstance(session, ftrack_api.session.Session):
return
StartTimer(session).register()

View file

@ -1,72 +0,0 @@
from pype.vendor import ftrack_api
from pype.ftrack import BaseEvent
class CollectEntities(BaseEvent):
priority = 1
def _launch(self, event):
entities = self.translate_event(event)
event['data']['entities_object'] = entities
return
def translate_event(self, event):
selection = event['data'].get('selection', [])
entities = list()
for entity in selection:
ent = self.session.get(
self.get_entity_type(entity),
entity.get('entityId')
)
entities.append(ent)
return entities
def get_entity_type(self, entity):
'''Return translated entity type tht can be used with API.'''
# Get entity type and make sure it is lower cased. Most places except
# the component tab in the Sidebar will use lower case notation.
entity_type = entity.get('entityType').replace('_', '').lower()
for schema in self.session.schemas:
alias_for = schema.get('alias_for')
if (
alias_for and isinstance(alias_for, str) and
alias_for.lower() == entity_type
):
return schema['id']
for schema in self.session.schemas:
if schema['id'].lower() == entity_type:
return schema['id']
raise ValueError(
'Unable to translate entity type: {0}.'.format(entity_type)
)
def register(self):
self.session.event_hub.subscribe(
'topic=ftrack.action.discover'
' and source.user.username={0}'.format(self.session.api_user),
self._launch,
priority=self.priority
)
self.session.event_hub.subscribe(
'topic=ftrack.action.launch'
' and source.user.username={0}'.format(self.session.api_user),
self._launch,
priority=self.priority
)
def register(session, **kw):
'''Register plugin. Called when used as an plugin.'''
if not isinstance(session, ftrack_api.session.Session):
return
CollectEntities(session).register()

View file

@ -153,6 +153,7 @@ class FtrackModule:
parent_menu.addMenu(self.menu)
def tray_start(self):
self.validate()
# Definition of visibility of each menu actions

View file

@ -66,6 +66,10 @@ class BaseAction(BaseHandler):
self.session, event
)
preactions_launched = self._handle_preactions(self.session, event)
if preactions_launched is False:
return
interface = self._interface(
self.session, *args
)

View file

@ -23,10 +23,11 @@ class AppAction(BaseHandler):
'''
type = 'Application'
preactions = ['start.timer']
def __init__(
self, session, label, name, executable,
variant=None, icon=None, description=None
variant=None, icon=None, description=None, preactions=[]
):
super().__init__(session)
'''Expects a ftrack_api.Session instance'''
@ -44,6 +45,7 @@ class AppAction(BaseHandler):
self.variant = variant
self.icon = icon
self.description = description
self.preactions.extend(preactions)
def register(self):
'''Registers the action, subscribing the discover and launch topics.'''
@ -117,6 +119,12 @@ class AppAction(BaseHandler):
self.session, event
)
preactions_launched = self._handle_preactions(
self.session, event
)
if preactions_launched is False:
return
response = self.launch(
self.session, *args
)
@ -148,25 +156,6 @@ class AppAction(BaseHandler):
entity = entities[0]
project_name = entity['project']['full_name']
# Validate Clockify settings if Clockify is required
clockify_timer = os.environ.get('CLOCKIFY_WORKSPACE', None)
if clockify_timer is not None:
from pype.clockify import ClockifyAPI
clockapi = ClockifyAPI()
if clockapi.verify_api() is False:
title = 'Launch message'
header = '# You Can\'t launch **any Application**'
message = (
'<p>You don\'t have set Clockify API'
' key in Clockify settings</p>'
)
items = [
{'type': 'label', 'value': header},
{'type': 'label', 'value': message}
]
self.show_interface(event, items, title)
return False
database = pypelib.get_avalon_database()
# Get current environments
@ -335,39 +324,6 @@ class AppAction(BaseHandler):
}
pass
# RUN TIMER IN FTRACK
username = event['source']['user']['username']
user_query = 'User where username is "{}"'.format(username)
user = session.query(user_query).one()
task = session.query('Task where id is {}'.format(entity['id'])).one()
self.log.info('Starting timer for task: ' + task['name'])
user.start_timer(task, force=True)
# RUN TIMER IN Clockify
if clockify_timer is not None:
task_type = task['type']['name']
project_name = task['project']['full_name']
def get_parents(entity):
output = []
if entity.entity_type.lower() == 'project':
return output
output.extend(get_parents(entity['parent']))
output.append(entity['name'])
return output
desc_items = get_parents(task['parent'])
desc_items.append(task['name'])
description = '/'.join(desc_items)
project_id = clockapi.get_project_id(project_name)
tag_ids = []
tag_ids.append(clockapi.get_tag_id(task_type))
clockapi.start_time_entry(
description, project_id, tag_ids=tag_ids
)
# Change status of task to In progress
config = get_config_data()

View file

@ -25,6 +25,7 @@ class BaseHandler(object):
priority = 100
# Type is just for logging purpose (e.g.: Action, Event, Application,...)
type = 'No-type'
preactions = []
def __init__(self, session):
'''Expects a ftrack_api.Session instance'''
@ -46,18 +47,7 @@ class BaseHandler(object):
else:
label = '{} {}'.format(self.label, self.variant)
try:
if hasattr(self, "role_list") and len(self.role_list) > 0:
username = self.session.api_user
user = self.session.query(
'User where username is "{}"'.format(username)
).one()
available = False
for role in user['user_security_roles']:
if role['security_role']['name'] in self.role_list:
available = True
break
if available is False:
raise MissingPermision
self._preregister()
start_time = time.perf_counter()
func(*args, **kwargs)
@ -119,6 +109,36 @@ class BaseHandler(object):
def reset_session(self):
self.session.reset()
def _preregister(self):
if hasattr(self, "role_list") and len(self.role_list) > 0:
username = self.session.api_user
user = self.session.query(
'User where username is "{}"'.format(username)
).one()
available = False
for role in user['user_security_roles']:
if role['security_role']['name'] in self.role_list:
available = True
break
if available is False:
raise MissingPermision
# Custom validations
result = self.preregister()
if result is True:
return
msg = "Pre-register conditions were not met"
if isinstance(result, str):
msg = result
raise Exception(msg)
def preregister(self):
'''
Preregister conditions.
Registration continues if returns True.
'''
return True
def register(self):
'''
Registers the action, subscribing the discover and launch topics.
@ -227,6 +247,10 @@ class BaseHandler(object):
self.session, event
)
preactions_launched = self._handle_preactions(self.session, event)
if preactions_launched is False:
return
interface = self._interface(
self.session, *args
)
@ -263,6 +287,47 @@ class BaseHandler(object):
'''
raise NotImplementedError()
def _handle_preactions(self, session, event):
# If preactions are not set
if len(self.preactions) == 0:
return True
# If no selection
selection = event.get('data', {}).get('selection', None)
if (selection is None):
return False
# If preactions were already started
if event['data'].get('preactions_launched', None) is True:
return True
# Launch preactions
for preaction in self.preactions:
event = ftrack_api.event.base.Event(
topic='ftrack.action.launch',
data=dict(
actionIdentifier=preaction,
selection=selection
),
source=dict(
user=dict(username=session.api_user)
)
)
session.event_hub.publish(event, on_error='ignore')
# Relaunch this action
event = ftrack_api.event.base.Event(
topic='ftrack.action.launch',
data=dict(
actionIdentifier=self.identifier,
selection=selection,
preactions_launched=True
),
source=dict(
user=dict(username=session.api_user)
)
)
session.event_hub.publish(event, on_error='ignore')
return False
def _interface(self, *args):
interface = self.interface(*args)
if interface:

View file

@ -2,6 +2,4 @@ from .idle_manager import IdleManager
def tray_init(tray_widget, main_widget):
manager = IdleManager()
manager.start()
return manager
return IdleManager()

View file

@ -17,8 +17,12 @@ class IdleManager(QtCore.QThread):
super(IdleManager, self).__init__()
self.log = Logger().get_logger(self.__class__.__name__)
self.signal_reset_timer.connect(self._reset_time)
self._failed = False
self._is_running = False
def tray_start(self):
self.start()
def add_time_signal(self, emit_time, signal):
""" If any module want to use IdleManager, need to use add_time_signal
:param emit_time: time when signal will be emitted
@ -30,6 +34,10 @@ class IdleManager(QtCore.QThread):
self.time_signals[emit_time] = []
self.time_signals[emit_time].append(signal)
@property
def failed(self):
return self._failed
@property
def is_running(self):
return self._is_running
@ -60,6 +68,8 @@ class IdleManager(QtCore.QThread):
thread_keyboard.signal_stop.emit()
thread_keyboard.terminate()
thread_keyboard.wait()
self._failed = True
self._is_running = False
self.log.info('IdleManager has stopped')

View file

@ -25,6 +25,7 @@ class TimersManager(metaclass=Singleton):
when user idles for a long time (set in presets).
"""
modules = []
failed = False
is_running = False
last_task = None