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 = '17.0.0')
@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()
GROOVY