Document toolboxDocument toolbox

This article is for Data Center. Visit Cloud

Run all effectors by schedule

This script can be used in ScriptRunner to run effectors by schedule. See ScriptRunner Jobs.

Run effectors
/*
 * This script allows to start specified or all effectors for chosen structure.
 * It will ping started effector process until it finishes.
 * It will NOT acknowledge the process at the end so the process will be available in the structure status bar process list.
 */

// params
def structureId = 14 // target structure ID
def effectorIds = [2, 1] // effector IDs to run or empty to run all effectors in structure
def processCheckInterval = 1000; // milliseconds

// Structure imports
import com.almworks.jira.structure.api.StructurePluginHelper
import com.almworks.jira.structure.api.auth.StructureAuth
import com.almworks.jira.structure.api.effector.instance.EffectorInstanceManager
import com.almworks.jira.structure.api.effector.process.EffectorProcessManager
import com.almworks.jira.structure.api.error.StructureException
import com.almworks.jira.structure.api.forest.ForestSpec
import com.almworks.jira.structure.api.forest.ForestService
import com.almworks.jira.structure.api.forest.ForestSource
import com.almworks.jira.structure.api.StructureComponents
import com.almworks.jira.structure.api.item.CoreIdentities
import com.almworks.jira.structure.api.permissions.PermissionLevel
import com.almworks.jira.structure.api.process.ProcessHandleManager
import com.almworks.jira.structure.api.process.ProcessStatus
import com.almworks.jira.structure.api.row.RowManager
import com.almworks.jira.structure.api.structure.StructureManager
import com.almworks.jira.structure.api.util.StructureUtil

// ScriptRunner imports
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import com.onresolve.scriptrunner.runner.ScriptRunnerImpl
// Atlassian import (might be available without the import in some instances, but better safe than sorry)
import com.atlassian.jira.component.ComponentAccessor

import static java.util.Arrays.asList

// switch to plugin
@Grab(group = 'com.almworks.jira.structure', module = 'structure-api', version = '*')
@WithPlugin('com.almworks.jira.structure')

class EffectorRunner {
  StructureComponents SC = ScriptRunnerImpl.getPluginComponent(StructureComponents)
  ForestService FS = SC.getForestService()
  RowManager RM = SC.getRowManager()
  StructureManager SM = SC.getStructureManager()
  ProcessHandleManager PHM = SC.getProcessHandleManager()
  // next components are not available from StructureComponents in Structure 6.x, but who knows :)
  EffectorInstanceManager EIM = ScriptRunnerImpl.getPluginComponent(EffectorInstanceManager)
  EffectorProcessManager EPM = ScriptRunnerImpl.getPluginComponent(EffectorProcessManager)

  def user = StructureAuth.getUser()
  ForestSource source

  def structureId
  def effectorIds
  def log
  def processCheckInterval

  EffectorRunner(def log, def structureId, def effectorIds, def processCheckInterval) {
    this.structureId = structureId
    this.effectorIds = effectorIds
    this.log = log
    this.processCheckInterval = processCheckInterval
    try {
      // check for structure existence and permissions
      SM.getStructure(structureId, PermissionLevel.VIEW)
      source = FS.getForestSource(ForestSpec.skeleton(structureId))
    } catch (StructureException ex) {
      log.error("Cant create forest source for structure id=${structureId} for user ${user}.\n${trimStackTrace(ex)}")
    }
  }

  def run() {
    def caption = "See logs for results."
    if (source == null) return caption
    def structureName = FS.getDisplayName(ForestSpec.skeleton(structureId))

    def effectors = getEffectorInstances()
    if (effectors.isEmpty()) {
      log.warn("No effectors were found.")
      return caption
    }

    if (!effectorIds.isEmpty() && effectorIds.size != effectors.size()) {
      return caption
    }

    log.warn("Starting to execute effectors for structure '${structureName}' for user ${user}")
    // print effectors list to run
    effectors.each {ef ->
      log.warn("\teffector: id=${ef.getId()} ${ef.getModuleKey()} ${ef.getParameters()}")
    }




    // start effectors process
    def processId = runEffectors(effectors)
    if (processId == null) return caption

    log.warn("Effector process id=${processId} started.")

    // await process finish
    if (awaitFinish(processId)) {
      // print produced effect records and errors
      printResults(processId)
      log.warn("Done.")
    } else {
      log.error("Failed.")
    }

    return caption
  }

  def printResults(def processId) {
    def records = EPM.getEffectRecords(processId)
    if (records.isEmpty()) {
      log.warn("No effect records were produced.")
    } else if (records.size() > 100) { // just not to spam
      log.warn("There are ${records.size()} applied effects.")
    } else {
      records.each {record ->
        // choose right log level for errors and applied effects
        if (record.isError()) {
          log.error(StructureUtil.getTextInCurrentUserLocale(record.getEffectMessage()))
        } else {
          log.warn(StructureUtil.getTextInCurrentUserLocale(record.getEffectMessage()))
        }
      }
    }

  }

  // This is blocking method will run until effectors process finish.
  def awaitFinish(def processId) {
    def handleId
    try {
      def process = EPM.getProcess(processId)
      handleId = process.getProcessHandleId()
    } catch (StructureException ex) {
      log.error("Failed to get process info\n.${trimStackTrace(ex)}")
      return false
    }

    def info = PHM.getInfo(handleId)
    if (info == null) return false

    def prevProgress = 0
    while (info != null && info.getStatus() != ProcessStatus.FINISHED && sleep()) {
      info = PHM.getInfo(handleId)
      if (info.percentComplete != prevProgress) {
        def process = EPM.getProcess(processId)
        log.warn("\tprocess complete ${info.getPercentComplete()}%. Status ${process.getStatus()}")
        prevProgress = info.getPercentComplete()
      }
    }
    return info != null
  }

  def sleep() {
    try {
      Thread.sleep(processCheckInterval)
      return true
    } catch (InterruptedException ex) {
      log.warn(${trimStackTrace(ex)})
      Thread.currentThread().interrupt()
      return false
    }
    return true
  }

  def runEffectors(def effectors) {
    try {
      return EPM.startProcess(effectors, structureId, false)
    } catch (StructureException ex) {
      log.error("Effector process validation failed.\n${trimStackTrace(ex)}")
      return null
    }
  }

  def getEffectorInstances() {
    def ids = effectorIds.isEmpty() ? findEffectors() : effectorIds
    def instances = []
    ids.each {id ->
      try {
        instances.add(EIM.getEffectorInstance(id))
      } catch (StructureException ex) {
        log.error("Unable to find effector with id=${id}.\n${trimStackTrace(ex)}")
      }
    }

    return instances
  }

  def findEffectors() {
    def effectorIds = []
    source.getLatest().getForest().forEach {row ->
      def rowId = row.left()
      def itemId = RM.getRow(rowId).getItemId()
      if (CoreIdentities.isEffector(itemId)) {
        effectorIds.add(itemId.getLongId())
      }
    }
    return effectorIds
  }

  def trimStackTrace(Exception e) {
    return asList(e.stackTrace).subList(0, 20).join('\n    ') + '\n    ...'
  }
}

new EffectorRunner(log, structureId, effectorIds, processCheckInterval).run()