def createNotes(self, selection, text, category): entityCount = len(selection) logging.info('Creating notes on {0} entities'.format(entityCount)) job = ftrack.createJob( 'Creating notes ({0} of {1})'.format(1, entityCount), 'running' ) try: for index, item in enumerate(selection, start=1): entityType = item['entityType'] entityId = item['entityId'] entity = None if index != 1: job.setDescription('Creating notes ({0} of {1})'.format(index, entityCount)) if entityType == 'show': entity = ftrack.Project(entityId) elif entityType == 'task': entity = ftrack.Task(entityId) elif entityType == 'assetversion': entity = ftrack.AssetVersion(entityId) if not entity: logging.warning( u'Entity ({0}, {1}) not a valid type, skipping..' .format(entityId, entityType) ) entity.createNote(text, category) except Exception: job.setStatus('failed') logging.info('Note creation completed.') job.setStatus('done')
def is_valid_selection(self, event): selection = event["data"].get("selection", []) if not selection: return entityType = selection[0]["entityType"] if entityType not in ["assetversion", "task"]: return False if entityType == "assetversion": version = ftrack.AssetVersion(selection[0]["entityId"]) # filter to image sequences and movies only if version.getAsset().getType().getShort() != "mov": return False if entityType == "task": task = ftrack.Task(selection[0]["entityId"]) # filter to tasks if task.getObjectType() != "Task": return False return True
def callback(event): """ Status Handler. Sets status of task to the updated status of the asset version. If status of task or asset version is updated to "Ready for Lighting" or "Ready for Comp", sends a message to the assigned user. """ for entity in event['data'].get('entities', []): entityType = entity.get('entityType') if entityType == 'assetversion' and entity['action'] == 'update': if 'keys' not in entity or 'statusid' not in entity.get('keys'): return version = ftrack.AssetVersion(id=entity['entityId']) status = version.getStatus() # Set the task status to that of the asset version. task = version.getTask() task.setStatus(status) if status.getName() == 'Ready for Lighting': findTask(task, 'Lighting') elif status.getName() == 'Ready for Comp': findTask(task, 'Compositing') elif entityType == 'task' and entity['action'] == 'update': if 'keys' not in entity or 'statusid' not in entity.get('keys'): return task = ftrack.Task(id=entity['entityId']) status = task.getStatus() if status.getName() == 'Ready for Lighting': findTask(task, 'Lighting') elif status.getName() == 'Ready for Comp': findTask(task, 'Compositing')
def _findLatestComponent(self, entityId, entityType, extension=''): '''Return latest published component from *entityId* and *entityType*. *extension* can be used to find suitable components by matching with their file system path. ''' if entityType == 'task': task = ftrack.Task(entityId) versions = task.getAssetVersions() elif entityType == 'assetversion': versions = [ftrack.AssetVersion(entityId)] else: self.logger.debug( ('Unable to find latest version from entityId={entityId} ' 'with entityType={entityType}.').format( entityId=entityId, entityType=entityType)) return None lastDate = None latestComponent = None for version in versions: for component in version.getComponents(): fileSystemPath = component.getFilesystemPath() if fileSystemPath and fileSystemPath.endswith(extension): if (lastDate is None or version.getDate() > lastDate): latestComponent = component lastDate = version.getDate() return latestComponent
def launch(self, event): if 'values' in event['data']: # Do something with the values or return a new form. values = event['data']['values'] data = event['data'] selection = data.get('selection', []) version = ftrack.AssetVersion(selection[0]['entityId']) success = True msg = 'Increased version number.' if not values['version_number']: success = False msg = 'No number was submitted.' else: if int(values['version_number']) <= 0: success = False msg = 'Negative or zero is not valid.' else: version.set('version', int(values['version_number'])) return {'success': success, 'message': msg} return { 'items': [{ 'label': 'Version number', 'type': 'number', 'name': 'version_number' }] }
def getVersionsInSelection(self, selection): '''Return list of versions in *selection*.''' versions = [] for item in selection: self.logger.info( 'Looking for versions on entity ({0}, {1})'.format( item['entityId'], item['entityType'])) if item['entityType'] == 'assetversion': versions.append(ftrack.AssetVersion(item['entityId'])) continue entity = None if item['entityType'] == 'show': entity = ftrack.Project(item['entityId']) elif item['entityType'] == 'task': entity = ftrack.Task(item['entityId']) if not entity: continue assets = entity.getAssets(includeChildren=True) self.logger.info('Found {0} assets on entity'.format(len(assets))) for asset in assets: assetVersions = asset.getVersions() self.logger.info('Found {0} versions on asset {1}'.format( len(assetVersions), asset.getId())) versions.extend(assetVersions) self.logger.info('Found {0} versions in selection'.format( len(versions))) return versions
def callback(event): """ This plugin sets the task status from the version status update. """ if '7bbf1c8a-b5f5-11e4-980e-040112b6a801' not in event['data']['parents']: return for entity in event['data'].get('entities', []): # Filter non-assetversions if entity.get('entityType' ) == 'assetversion' and entity['action'] == 'update': version = ftrack.AssetVersion(id=entity.get('entityId')) asset = version.getAsset() if version.getVersion() != 1 or asset.getName() != 'hobsoft': return shot = asset.getParent() task = None if shot.getTasks(taskTypes=['painting']): task = shot.getTasks(taskTypes=['painting'])[0] else: task_type = utils.GetTaskTypeByName('painting') status = utils.GetStatusByName('ready') task = shot.createTask('painting', taskType=task_type, taskStatus=status) print 'hobsoft ingest on %s' % task
def launch(self, event): ''' Passes launch information to the launcher. Registers the application to launch and any additional data required for the launch. ''' # Prevent further processing by other listeners. event.stop() # The application selected. application_identifier = event['data']['applicationIdentifier'] # If we have an asset_version selected, pass it to the launcher as context. context = event['data'].copy() context['source'] = event['source'] if context['selection'][0]['entityType'] == 'asset_version' or context[ 'selection'][0]['entityType'] == 'assetversion': assetVersion = ftrack.AssetVersion( context['selection'][0]['entityId']) context['selection'] = [{ 'entityId': context['selection'][0]['entityId'], 'entityType': context['selection'][0]['entityType'] }] return self.launcher.launch(application_identifier, context)
def getEntityChildren(entityType=None, entity=None): '''Get all children for Review Session''' reviewEntities = entity.reviewSessionObjects() lst = [] for i in reviewEntities: v = ftrack.AssetVersion(id=i.get('version_id')) lst.append((v, i)) #getId version_id return lst
def publishAsset(self, iAObj=None): '''Publish the asset defined by the provided *iAObj*.''' panelComInstance = panelcom.PanelComInstance.instance() if hasattr(iAObj, 'customComponentName'): componentName = iAObj.customComponentName else: componentName = '3dsmax' if iAObj.options.get('exportMode') == 'Selection': # Check if the selection is empty and abort if it is. if selectionEmpty(): self._reportError('No objects selected to publish.') exportSelectedMode = True else: exportSelectedMode = False publishedComponents = [] temporaryPath = os.path.join(MaxPlus.PathManager.GetTempDir() + uuid.uuid4().hex + '.max') if exportSelectedMode: MaxPlus.FileManager.SaveSelected(temporaryPath) else: MaxPlus.FileManager.Save(temporaryPath, False, False) publishedComponents.append( FTComponent(componentname=componentName, path=temporaryPath)) # Handle dependencies. dependenciesVersion = [] for node in getAllFtrackAssetHelperNodes(recurse=True): obj = node.Object dependencyAssetId = obj.ParameterBlock.assetId.Value if dependencyAssetId: dependencyVersion = ftrack.AssetVersion(dependencyAssetId) dependenciesVersion.append(dependencyVersion) if dependenciesVersion: currentVersion = ftrack.AssetVersion(iAObj.assetVersionId) currentVersion.addUsesVersions(versions=dependenciesVersion) panelComInstance.emitPublishProgressStep() return publishedComponents, 'Published ' + iAObj.assetType + ' asset'
def create_job(event): values = event['data']['values'] job = ftrack.createJob('Generating Titles', 'queued', ftrack.User(id=event['source']['user']['id'])) job.setStatus('running') image_magick_dir = r'K:\development\tools\image-magick' errors = '' # Generateting sources and destinations for item in event['data']['selection']: try: entity = ftrack.AssetVersion(item['entityId']) # adding path to errors path = '' parents = entity.getParents() parents.reverse() for p in parents: path += p.getName() + '/' path += 'v' + str(entity.getVersion()).zfill(3) input_file = entity.getComponent().getFilesystemPath() output_file = ntpath.basename(input_file) # get version string version_string = '.v' + version_get(input_file, 'v')[1] if values['strip_version'] == 'True': output_file = output_file.replace(version_string, '') # get titles text south_west_text = 'Task: "%s"' % path[:-5] south_east_text = 'Version: "%s"' % version_string[1:] north_west_text = 'Status: "%s"' % entity.getStatus().getName() if entity.getAsset().getType().getShort() == 'img': src = os.listdir(os.path.dirname(input_file))[0] input_file = os.path.join(os.path.dirname(input_file), src) output_file = re.sub(r'.%04d', '', output_file) output_file = os.path.splitext(output_file)[0] + '.png' output_file = os.path.join(values['output_directory'], output_file) generate_title(image_magick_dir, output_file, input_file, '25', south_west_text, south_east_text, north_west_text) except: errors += path + '\n' errors += traceback.format_exc() + '\n' # generate error report if errors: temp_txt = os.path.join(values['output_directory'], 'errors.txt') f = open(temp_txt, 'w') f.write(errors) f.close() job.setStatus('done')
def process(self, instance): import os import subprocess from Deadline import Scripting import ftrack # Get FFmpeg executable from Deadline plugin config config = Scripting.RepositoryUtils.GetPluginConfig("FFmpeg") executables = config.GetConfigEntry("FFmpeg_RenderExecutable") existing_executables = [] for exe in executables.split(";"): if os.path.exists(exe): existing_executables.append(exe) msg = "No FFmpeg executable found in plugin configuration;" msg += "\n" + executables assert existing_executables, msg # Get random file from instance input_path = instance.data["files"].itervalues().next()[0] # Generate ffmpeg arguments and process input_args = [] if "img.*" in instance.data["families"]: vf = "scale=-1:108" if os.path.splitext(input_path)[1] == ".exr": vf += ",lutrgb=r=gammaval(0.45454545):" vf += "g=gammaval(0.45454545):" vf += "b=gammaval(0.45454545)" input_args.extend(["-vf", vf]) elif "mov.*" in instance.data["families"]: input_args.extend(["-vf", "thumbnail,scale=-1:108", "-frames:v", "1"]) output_path = os.path.splitext(input_path)[0] output_path += '_thumbnail.png' self.log.info("input path: " + input_path) self.log.info("output path: " + output_path) args = [existing_executables[0]] args.extend(['-i', input_path] + input_args + ['-y', output_path]) self.log.info("Args: %s" % args) subprocess.call(args) # Uploading thumbnail and clean up disk files. asset_version = instance.data('ftrackAssetVersion') version = ftrack.AssetVersion(asset_version['id']) version.createThumbnail(output_path) if os.path.exists(output_path): os.remove(output_path)
def launch(self, event): selection = event['data'].get('selection', []) version = ftrack.AssetVersion(selection[0]['entityId']) asset = version.getAsset() for v in asset.getVersions(): if not v.get('ispublished'): v.delete() return {'success': True, 'message': 'removed hidden versions'}
def callback(event): """ This plugin adds a newly uploaded asset version with status "Pending Internal Review" to the latest review list. """ for entity in event['data'].get('entities', []): if entity.get('entityType' ) == 'assetversion' and entity['action'] == 'update': assetVersion = ftrack.AssetVersion(id=entity.get('entityId')) if assetVersion.getStatus().getName() == 'Pending Internal Review': if assetVersion.get('taskid'): addToList(assetVersion.get('taskid'), assetVersion)
def process(self, context): import os import json import ftrack job = context.data("deadlineJob") value = job.GetJobExtraInfoKeyValueWithDefault("PyblishInstanceData", "") if not value: return instance_data = json.loads(value) version = instance_data["ftrackAssetVersion"] if not instance_data["ftrackStatusUpdate"]: return # since "render" families will always produce a secondary job if os.path.splitext(instance_data["family"])[1] in [".ifd"]: return event = context.data["deadlineEvent"] event_dict = { "OnJobSubmitted": "Render Queued", "OnJobStarted": "Render", "OnJobFinished": "Render Complete", "OnJobRequeued": "Render Queued", "OnJobFailed": "Render Failed", "OnJobSuspended": "Render Queued", "OnJobResumed": "Render", "OnJobPended": "Render Queued", "OnJobReleased": "Render Queued" } if event in event_dict.keys(): project_id = context.data["ftrackData"]["Project"]["id"] ft_project = ftrack.Project(project_id) statuses = ft_project.getVersionStatuses() # Need to find the status by name ft_status = None for status in statuses: if status.getName().lower() == event_dict[event].lower(): ft_status = status break ftrack.AssetVersion(version["id"]).setStatus(ft_status) self.log.info("Setting ftrack version to %s" % event_dict[event])
def callback(event): """ This plugin sets the task status from the version status update. """ for entity in event['data'].get('entities', []): # Filter non-assetversions if entity.get('entityType' ) == 'assetversion' and entity['action'] == 'update': version = ftrack.AssetVersion(id=entity.get('entityId')) version_status = version.getStatus() try: task = ftrack.Task(version.get('taskid')) except: return valid_types = ['img', 'mov', 'cache', 'render'] if version.getAsset().getType().getShort() not in valid_types: return task_status = utils.GetStatusByName( version_status.get('name').lower()) # Filter to versions with status change to "render complete" if version_status.get('name').lower() == 'render complete': task_status = utils.GetStatusByName('artist review') # Filter to versions with status change to "render queued" if version_status.get('name').lower() == 'render queued': task_status = utils.GetStatusByName('render') # Proceed if the task status was set if task_status: # Get path to task path = task.get('name') for p in task.getParents(): path = p.get('name') + '/' + path # Setting task status try: task.setStatus(task_status) except Exception as e: print '%s status couldnt be set: %s' % (path, e) else: print '%s updated to "%s"' % (path, task_status.get('name'))
def callback(event): """ This plugin sets the task status from the version status update. """ for entity in event['data'].get('entities', []): # Filter non-assetversions if entity.get('entityType' ) == 'assetversion' and entity['action'] == 'update': version = ftrack.AssetVersion(id=entity.get('entityId')) version_status = version.getStatus() # Filter to versions with status change to "render complete" if version_status.get('name').lower() == 'approved': for component in version.getComponents(): print component
def _getApplicationLaunchCommand(self, application, context=None): ''' Returns a command to launch the selected application. If a file is selected to open, it will be appended to the command. ''' # Inherit the base command from the ApplicationLauncher. command = super(ApplicationLauncher, self)._getApplicationLaunchCommand( application, context)[0] # Figure out if the command should be started with a file path. if command is not None and context is not None: # If our selection is an asset_version and its type is an image sequence, get its component. selection = context['selection'][0] if selection['entityType'] == 'asset_version' or selection[ 'entityType'] == 'assetversion': self.logger.debug( u'Launching action with context {0!r}'.format(context)) try: entityId = selection['entityId'] aVersion = ftrack.AssetVersion(entityId) if aVersion.getAsset().getType().getShort() == 'img': component = None try: components = aVersion.getComponents() for c in components: if c.getName() == 'Server link': component = c except: pass # If we have a component, append its path to the command. if component: path = component.getFilesystemPath() self.logger.info( u'Launching application with file {0!r}'. format(path)) command = command + ' ' + path else: self.logger.warning( 'Unable to find an appropriate component when ' 'launching with latest version.') except: pass return command
def process(self, instance): # skipping instance if ftrackData isn't present if not instance.context.has_data('ftrackData'): self.log.info('No ftrackData present. Skipping this instance') return ftrack_data = instance.context.data('ftrackData') # skipping the call up project if ftrack_data['Project']['code'] == 'the_call_up': return # skipping instance if asset version isn't present if 'AssetVersion' not in ftrack_data: self.log.info('No AssetVersion present. Skipping this instance') return # skipping instance if ftrackComponents isn't present if not instance.has_data('ftrackComponents'): self.log.info( 'No ftrackComponents present. Skipping this instance') return version_id = ftrack_data['AssetVersion']['id'] asset_version = ftrack.AssetVersion(id=version_id) online_components = asset_version.getComponents() local_components = instance.data('ftrackComponents') for local_c in local_components: local_component = local_components[local_c] for online_c in online_components: # checking name matching if local_c == online_c.getName(): # checking value matching path = local_component['path'] if path != online_c.getFile(): msg = "Component exists, but values aren't the same:" msg += "\n\nComponent: %s" % local_c msg += "\n\nLocal value: %s" % path msg += "\n\nOnline value: %s" % online_c.getFile() raise ValueError(msg) else: self.log.info('Component exists: %s' % local_c)
def getEntity(entityId, entityType): '''Return entity based on *entityId* and *entityType*.''' entity = None if entityType == ftrack.Project._type: entity = ftrack.Project(entityId) elif entityType == ftrack.Task._type: entity = ftrack.Task(entityId) elif entityType == 'assetversion': entity = ftrack.AssetVersion(entityId) if not entity: logging.warning( u'Entity ({0}, {1}) not a valid type, skipping..'.format( entityId, entityType)) return entity
def get_components(event, asset_types): # finding components data = [] for item in event["data"].get("selection", []): selection = event["data"]["selection"] entity_type = selection[0]["entityType"] # get all components on version if entity_type == "assetversion": version = ftrack.AssetVersion(item["entityId"]) if not version.get("ispublished"): version.publish() for c in version.getComponents(): data.append({ "label": c.getName(), "value": { 'name': c.getName(), 'filename': get_file_for_component(c) } }) # get all components on all valid versions if entity_type == "task": task = ftrack.Task(selection[0]["entityId"]) for asset in task.getAssets(assetTypes=asset_types): for version in asset.getVersions(): padded_version = str(version.getVersion()).zfill(3) for component in version.getComponents(): label = "v" + padded_version label += " - " + asset.getType().getName() label += " - " + component.getName() data.append({ "label": label, "value": { 'name': component.getName(), 'filename': get_file_for_component(component) } }) data = sorted(data, key=itemgetter("label"), reverse=True) return data
def importAsset(self, iAObj=None): '''Create nuke read node from *iAObj.''' if iAObj.filePath.endswith('nk'): nuke.nodePaste(iAObj.filePath) return else: resultingNode = nuke.createNode('Read', inpanel=False) resultingNode['name'].setValue( HelpFunctions.safeString(iAObj.assetName) + '_' + HelpFunctions.safeString(iAObj.componentName)) self.addFTab(resultingNode) # Compute frame range # TODO: Store these attributes on the component for easy access. resultingNode['file'].fromUserText( HelpFunctions.safeString(iAObj.filePath)) start, end = self.getStartEndFrames(iAObj) resultingNode['first'].setValue(start) resultingNode['origfirst'].setValue(start) resultingNode['last'].setValue(end) resultingNode['origlast'].setValue(end) proxyPath = '' assetVersion = ftrack.AssetVersion(iAObj.assetVersionId) try: proxyPath = assetVersion.getComponent(name='proxy').getImportPath() except: pass try: proxyPath = assetVersion.getComponent(name=iAObj.componentName + '_proxy').getImportPath() except: pass if proxyPath != '': resultingNode['proxy'].fromUserText(proxyPath) self.setFTab(resultingNode, iAObj) return 'Imported %s asset' % iAObj.componentName
def setFTab(self, resultingNode, iAObj): componentId = ftrack.Component( iAObj.componentId).getEntityRef() assetVersionId = ftrack.AssetVersion( iAObj.assetVersionId).getEntityRef() components = { 'componentId': HelpFunctions.safeString(componentId), 'componentName': HelpFunctions.safeString(iAObj.componentName), 'assetVersionId': HelpFunctions.safeString(assetVersionId), 'assetVersion': HelpFunctions.safeString(iAObj.assetVersion), 'assetName': HelpFunctions.safeString(iAObj.assetName), 'assetType': HelpFunctions.safeString(iAObj.assetType), 'assetId': HelpFunctions.safeString(iAObj.assetId) } for comp in components: resultingNode.parm(comp).set(components[comp])
def discover(event): data = event['data'] for item in data['selection']: try: asset = ftrack.AssetVersion(item['entityId']).getAsset() if asset.getType().getShort() not in ['img', 'mov']: return except: return return { 'items': [{ 'label': 'Generate Titles', 'actionIdentifier': 'generate_titles' }] }
def launch(self, event): '''Callback method for action.''' selection = event['data'].get('selection', []) self.logger.info(u'Launching action with selection {0}'.format(selection)) job = ftrack.createJob(description="Push thumbnails to parents", status="running") try: ftrack.EVENT_HUB.publishReply( event, data={ 'success': True, 'message': 'Created job for updating thumbnails!' } ) for entity in selection: if entity['entityType'] == 'assetversion': entity = ftrack.AssetVersion(entity['entityId']) try: parent = entity.getTask() except: parent = entity.getParent().getParent() elif entity['entityType'] == 'task': entity = ftrack.Task(entity['entityId']) parent = entity.getParent() thumbid = entity.get('thumbid') if thumbid: parent.set('thumbid', value=thumbid) # inform the user that the job is done job.setStatus('done') except: # fail the job if something goes wrong job.setStatus('failed') raise return { 'success': True, 'message': 'Created job for updating thumbnails!' }
def setFTab(self, resultingNode, iAObj): componentId = ftrack.Component(iAObj.componentId).getEntityRef() assetVersionId = ftrack.AssetVersion( iAObj.assetVersionId).getEntityRef() resultingNode.knob('assetId').setValue( HelpFunctions.safeString(iAObj.assetId)) resultingNode.knob('componentId').setValue( HelpFunctions.safeString(componentId)) resultingNode.knob('componentName').setValue( HelpFunctions.safeString(iAObj.componentName)) resultingNode.knob('assetVersionId').setValue( HelpFunctions.safeString(assetVersionId)) resultingNode.knob('assetVersion').setValue( HelpFunctions.safeString(iAObj.assetVersion)) resultingNode.knob('assetName').setValue( HelpFunctions.safeString(iAObj.assetName)) resultingNode.knob('assetType').setValue( HelpFunctions.safeString(iAObj.assetType))
def callback(event): """ This plugin sets the task status from the version status update. """ for entity in event['data'].get('entities', []): if (entity.get('entityType') == 'reviewsessionobject' and entity['action'] == 'add'): version = None try: version_id = entity['changes']['version_id']['new'] version = ftrack.AssetVersion(version_id) except: return user = ftrack.User(event['source']['user']['id']) os.environ['LOGNAME'] = user.getUsername() myclass = Thread(version, user) myclass.start()
def get_dependencies(): dependencies = {} ft_attributes = [ 'assetVersionId', ] for node in nuke.allNodes(): for attr in ft_attributes: attribute = node.knob(attr) if attribute: knob_value = attribute.value() if 'ftrack' in knob_value: version_id = knob_value.split('ftrack://')[-1].split( '?')[0] dependency_version = ftrack.AssetVersion(version_id) print 'dependency %s found' % dependency_version dependencies[node['name'].value()] = dependency_version return dependencies
def process(self, instance): # skipping instance if ftrackData isn't present if not instance.context.has_data('ftrackData'): self.log.info('No ftrackData present. Skipping this instance') return # skipping instance if ftrackComponents isn't present if not instance.has_data('ftrackComponents'): self.log.info('No ftrackComponents present.\ Skipping this instance') return asset_version = instance.data('ftrackAssetVersion') # creating components version = ftrack.AssetVersion(asset_version['id']) components = instance.data('ftrackComponents') for component_name in instance.data('ftrackComponents'): # creating component try: path = components[component_name]['path'] except: return try: version.createComponent(name=component_name, path=path) self.log.info('Creating "%s" component.' % component_name) except: pass # make reviewable if 'reviewable' in components[component_name]: upload = True for component in version.getComponents(): if component_name in ('ftrackreview-mp4', 'ftrackreview-webm'): upload = False break if upload: ftrack.Review.makeReviewable(version, path)
def launch(self, event): '''Handle *event*. event['data'] should contain: *applicationIdentifier* to identify which application to start. ''' # Prevent further processing by other listeners. # TODO: Only do this when actually have managed to launch a relevant # application. event.stop() applicationIdentifier = (event['data']['applicationIdentifier']) context = event['data'].copy() context['source'] = event['source'] selection = context.get('selection', []) # If the selected entity is an asset version, change the selection # to parent task/shot instead since it is not possible to publish # to an asset version in ftrack connect. if (selection and selection[0]['entityType'] == 'assetversion'): assetVersion = ftrack.AssetVersion(selection[0]['entityId']) entityId = assetVersion.get('taskid') if not entityId: asset = assetVersion.parent() entity = asset.parent() entityId = entity.getId() context['selection'] = [{ 'entityId': entityId, 'entityType': 'task' }] return self.launcher.launch(applicationIdentifier, context)