Is there a way to automatically change epic state to done when all the linked stories and tasks are complete - jira

I am new to JIRA and Kanban. I was expecting that when I create an epic and link some stories and tasks to it. The status of the epic will automatically change (e.g. to done) when all the stories and tasks linked to it are done. But it seems this is not the case. I can move the epic from the Backlog to the Done column even when its linked tasks and stories are still in the backlog. Is there a way to make JIRA prevent that from happening?

I have been working on something similar. My intention was to set assigne of all linked issues of another one to a specific user when the status changes to a specific state.
I did this with a postfunction of the workflow of type: "Set Field Value to constant or Groovy expression"
In your situation I would do the following:
go to "Close" transition, and click configure.
select postfunctions, and add the type i told you.
mark the chekbox that says execute only if condition is true
set your condition. Probably something like issue.epic=your epic.
Then you add your script, where you recover alll the issues linked to the epic, and check their status.
Create your logic so that if everithing is as it should be, you just change the status, using MutableIssue object.
remember that a field is going to be modified by this script, and i guess you cant choose status as field to be set. If this happens, choose summary, and store the current value, and use it to end your script, and set the summary value, whtih the same you had.
Publish your workflow.
Excuse me if it is not clear, but is difficult to explain. Let me know if you need somenthing else.
PD: If you just want to do this at some specific moment and not for every epics automatically, just add Script Runner plugin, and run your script in the console. Much easier.
Regards

Maybe it helps you:
I used jira with system language set to "Russian" (and i'm not good in groovy), that's why script below contains language dependencies (you should edit code if you use differ from my jira system language! At least change )
Use Scrip Runner plugin
Create "Custom listener" and paste code (code is not so good as can be but it's working):
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.issue.IssueManager;
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.issue.link.IssueLinkManager;
import com.atlassian.jira.issue.link.IssueLink;
import com.atlassian.jira.issue.ModifiedValue;
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder;
import com.atlassian.jira.issue.customfields.option.Options;
import com.atlassian.jira.issue.customfields.option.Option;
import com.atlassian.jira.issue.fields.config.FieldConfig;
import com.atlassian.jira.issue.customfields.manager.OptionsManager;
import com.atlassian.jira.ComponentManager;
ComponentManager componentManager = ComponentManager.getInstance();
def groupMan = ComponentAccessor.getGroupManager()
def authCon = ComponentAccessor.getJiraAuthenticationContext()
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def changeHolder = new DefaultIssueChangeHolder();
IssueManager issueManager = ComponentAccessor.getIssueManager();
OptionsManager optionsManager = componentManager.getComponentInstanceOfType(OptionsManager.class);
IssueLinkManager issueLinkManager = ComponentAccessor.getIssueLinkManager()
def curUser = authCon.getUser()
def issue = event.issue
def epicLinkCf = customFieldManager.getCustomFieldObjects(issue).find {it.name == 'Epic Link'}
if(!epicLinkCf) {log.warn "No Epic Link field"; return}
log.warn "Existing Epic Link: ${epicLinkCf.getValue(issue)}"
String epicIssue = epicLinkCf.getValue(issue)
Issue epic = issueManager.getIssueObject(epicIssue) // epicKey is passed into your script
// Check if Epic link is exist
if(!epic)
return true
def newEpicState = "Сделано"
log.warn "Epic: " + epic
List<IssueLink> allOutIssueLink = issueLinkManager.getOutwardLinks(epic.getId());
for (Iterator<IssueLink> outIterator = allOutIssueLink.iterator(); outIterator.hasNext();) {
IssueLink issueLink = (IssueLink) outIterator.next();
log.warn "child link type: " + issueLink.getIssueLinkType().getName()
// Check status of all issues from epic
if (issueLink.getIssueLinkType().getName() == "Epic-Story Link") {
Issue chIssue = issueLink.getDestinationObject();
log.warn "child state: " + chIssue.getStatusObject().getName()
if(chIssue.getStatusObject().getName() == "В процессе") {
newEpicState = "В процессе"
} else if (chIssue.getStatusObject().getName() != "Закрыто" && newEpicState != "В процессе") {
newEpicState = "Сделать"
}
}
}
def epicStatusCf = customFieldManager.getCustomFieldObjects(epic).find {it.name == 'Epic Status'}
log.warn "Current epic status: " + epicStatusCf.getValue(epic)
FieldConfig epicStatusFieldConfig = epicStatusCf.getRelevantConfig(epic);
String oldStatus = epicStatusCf.getValue(epic)
log.warn "New epic status: " + newEpicState
// Set new status if it necessary
if (oldStatus != newEpicState) {
Options epicStatusOptions = optionsManager.getOptions(epicStatusFieldConfig);
Option epicStatusDoneOption = epicStatusOptions.getOptionForValue(newEpicState, null);
epicStatusCf.updateValue(null, epic, new ModifiedValue(epic.getCustomFieldValue(epicStatusCf),epicStatusDoneOption),changeHolder)
log.warn "Epic status is updated!"
}

If you're using Scriptrunner then you should be good to use the scriptrunner code. Please check this code from scriptrunner:
// Add the next line as a condition to the script listener, so it gets executed only for epic issues, the line must be written uncommented:
// issue.isEpic
// Check if the resolution has been changed
def resolutionChange = changelog.items.find {
(it as Map).field == 'resolution'
} as Map
logger.info("The resolution change of issue '${issue.key}': ${resolutionChange}.")
if (!resolutionChange) {
logger.info("The resolution didn't change.")
return
}
// Compute the 'Epic Status' value to set based on the resolution value
def newEpicStatusValue = (resolutionChange.toString == 'Done') ? 'Done' : 'To Do'
// Get the 'Epic Status' field ID
final epicStatusField = get("/rest/api/2/field")
.asObject(List)
.body
.find {
(it as Map).name == 'Epic Status'
} as Map
def epicStatusFieldId = epicStatusField.id
logger.info("Updating Epic Status field (${epicStatusFieldId}) to '${newEpicStatusValue}'.")
// Update the 'Epic Status' field value
put("/rest/api/2/issue/${issue.key}")
.queryString("overrideScreenSecurity", Boolean.TRUE)
.header("Content-Type", "application/json")
.body([
fields: [
(epicStatusFieldId): [value: newEpicStatusValue]
]
])
.asString()
You can automate this code your post-function or automation in jira. Please find the further details in this link.

Related

From deprecated cliOnline to OnlineNodeCommand

So I created this groovy script but read that the cliOnline() command is deprecated.
But I can't seem to figure out how to actually change my code to use hudson.model.Hudson.instance.OnlineNodeCommand()?
deprecated cliOnline
def nodes_checklist = ["PD100069", "PD100070", "PD100090", "PD10756"]; // List of nodes which should be turned online
def jenkinsNodes = jenkins.model.Jenkins.instance.getNodes() // Get all existing nodes on this Jenkins URL
for(def node_checker: nodes_checklist) {
for(def node: jenkinsNodes) {
if(node.getNodeName().contains(node_checker)) {
println "The node " + node.getNodeName() + "'s offline status: " + node.toComputer().isOffline()
if (node.toComputer().isOffline()){
println "Turning " + node.getNodeName() + " online"
node.toComputer().cliOnline() // If node is offline, turn it online
println node.getNodeName() + "'s online status: " node.toComputer().isOnline()
}
}
}
}
Does anyone know how to rewrite this to use the non-deprecated version?
If you look at this depricated method, it simply calls a non depricated method setTemporarilyOffline(boolean temporarilyOffline, OfflineCause cause). So not sure why this was depricated. Anyway instead of using cliOnline() you can use setTemporarilyOffline. Check the following.
node.getComputer().setTemporarilyOffline(false, null)
Some proper code with a proper cause. Cause is not really needed when setting the node online though.
import hudson.slaves.OfflineCause.UserCause
def jenkinsNodes = Jenkins.instance.getNodes()
for(def node: jenkinsNodes) {
if (node.getComputer().isTemporarilyOffline()){
node.getComputer().setTemporarilyOffline(false, null)
}
}
Setting to temporarily offline
UserCause cause = new UserCause(User.current(), "This is a automated process!!")
node.getComputer().setTemporarilyOffline(true, cause)

Install Snyk in Jenkins "Global Tool Configuration" using groovy

I'm trying to add a Snyk installation to Jenkins using groovy. The plugin is installed and I can see the installation option in Global Tool Configuration:
The problem is the Descriptor is not available until I manually add the installer and click Save. If I don't do this task manually, which I want to prevent, it causes my code to fail with the following error message "Cannot invoke method setInstallations() on null object"
My code:
import hudson.model.*
import jenkins.model.*
import hudson.tools.*
import hudson.tasks.*
import io.snyk.jenkins.tools.SnykInstaller
import io.snyk.jenkins.tools.SnykInstallation
def snyk_name = "Snyk"
def snyk_home = ""
def snyk_installer = new SnykInstaller("", "latest", 24)
def snyk_properties = new InstallSourceProperty([snyk_installer])
def instance = Jenkins.getInstance()
println("[init.groovy.d] START Configuring Snyk Installation...")
// Get the GlobalConfiguration descriptor of Snyk plugin.
def snyk_conf = instance.getDescriptor("io.snyk.jenkins.SnykStepBuilder.SnykStepBuilderDescriptor")
def snyk_inst = new SnykInstallation(
snyk_name,
snyk_home,
[snyk_properties]
)
// Only add the new Snyk setting if it does not exist - do not overwrite existing config
def snyk_installations = snyk_conf.getInstallations()
def snyk_inst_exists = false
snyk_installations.each {
installation = (SnykInstallation) it
if (snyk_inst.getName() == installation.getName()) {
snyk_inst_exists = true
println("Found existing installation: " + installation.getName())
}
}
if (!snyk_inst_exists) {
snyk_installations += snyk_inst
snyk_conf.setInstallations((SnykInstallation[]) snyk_installations)
snyk_conf.save()
}
// Save the state
instance.save()
println("[init.groovy.d] END")
Is there any way to do what I want programmatically?
After testing your groovy on my local Jenkins (v 2.263.1) I came up with the below which then worked for me:
import hudson.model.*
import jenkins.model.*
import hudson.tools.*
import hudson.tasks.*
import io.snyk.jenkins.tools.*
def instance = Jenkins.getInstance()
def snyk_name = "SnykLatest"
def snyk_home = ""
def snyk_installer = new SnykInstaller("", "latest", 24L, null)
def snyk_properties = new InstallSourceProperty([snyk_installer])
println("[init.groovy.d] START Configuring Snyk Installation...")
// Get the GlobalConfiguration descriptor of Snyk plugin.
def snyk_conf = instance.getDescriptor("io.snyk.jenkins.tools.SnykInstallation")
def snyk_inst = new SnykInstallation(
snyk_name,
snyk_home,
[snyk_properties]
)
// Only add the new Snyk setting if it does not exist - do not overwrite existing config
def snyk_installations = snyk_conf.getInstallations()
def snyk_inst_exists = false
snyk_installations.each {
installation = (SnykInstallation) it
if (snyk_inst.getName() == installation.getName()) {
snyk_inst_exists = true
println("Found existing installation: " + installation.getName())
}
}
if (!snyk_inst_exists) {
snyk_installations += snyk_inst
snyk_conf.setInstallations((SnykInstallation[]) snyk_installations)
snyk_conf.save()
}
// Save the state
instance.save()
println("[init.groovy.d] END")
In basic Terms the SnykInstaller was expecting 4 values not 3. Groovy also took the 3rd value as an Integer when it was expecting a Long Value.
References:
https://javadoc.jenkins.io/plugin/snyk-security-scanner/io/snyk/jenkins/tools/SnykInstaller.html
https://github.com/jenkinsci/snyk-security-scanner-plugin/blob/master/src/main/java/io/snyk/jenkins/tools/SnykInstaller.java
https://github.com/jenkinsci/snyk-security-scanner-plugin/blob/master/src/main/java/io/snyk/jenkins/tools/PlatformItem.java

Verifying Jenkins calls while testing pipeline code

I am writing a Jenkins pipeline library, and am having some difficulties with mocking/validating an existing Jenkins pipeline step.
I am using jenkins-spock by homeaway to unit test, but I think my problem is more Spock related.
import com.homeaway.devtools.jenkins.testing.JenkinsPipelineSpecification
import com.company.pipeline.providers.BuildLogProvider
class PublishBuildLogSpec extends JenkinsPipelineSpecification {
BuildLogProvider buildLogProvider = Mock()
PublishBuildLog publishBuildLog
def setup () {
publishBuildLog = new PublishBuildLog(buildLogProvider: buildLogProvider)
explicitlyMockPipelineStep('writeFile')
}
def "Gets the log file contents for a specific job and build"() {
when:
"the call method is executed with the jobName and buildNumber parameters set"
publishBuildLog.call("JOBNAME", "42")
then:
"the getBuildLog on the buildLogProvider is called with those parameters"
1 * buildLogProvider.getBuildLog("JOBNAME", "42")
}
def "the contents of log file is written to the workspace"() {
given:
"getBuildLog returns specific contents"
def logFileText = "Example Log File Text"
buildLogProvider.getBuildLog(_, _) >> logFileText
when:
"publishBuildLog.call is executed"
publishBuildLog.call(_, _)
then:
"the specific contents is passed to the writeFile step"
1 * getPipelineMock("writeFile").call([file: _ , text: logFileText])
}
}
This is my unit test. I am attempting to say that writeFile is called with the text matching the contents of logFileText, ignoring what the other parameters are. I have tried numerous combinations, but always seem to get the same or similar response to response of:
Too few invocations for:
1 * getPipelineMock("writeFile").call([file: _ , text: "Example Log File Text"]) (0 invocations)
Unmatched invocations (ordered by similarity):
1 * (explicit) getPipelineMock("writeFile").call(['file':'filename', 'text':'Example Log File Text'])
This is to test this class
import com.company.pipeline.providers.BuildLogProvider
class PublishBuildLog {
BuildLogProvider buildLogProvider = new BuildLogProvider()
void setBuildLogProvider(BuildLogProvider buildLogProvider) {
this.buildLogProvider = buildLogProvider
}
def call(def jobName, def buildNumber) {
def contents = buildLogProvider.getBuildLog(jobName, buildNumber)
writeFile(file: "filename", text: contents)
}
}
I am at a loss as to how to validate this call. I have a lot of experience with Java and Junit, but I am relatively new to Spock.
How can I verify this?
For me your test passes. But there is one thing I find strange: You use jokers in a when: block where you should really use concrete parameters like in the first feature method:
when: "publishBuildLog.call is executed"
publishBuildLog.call(_, _)
Instead you should write:
when: "publishBuildLog.call is executed"
publishBuildLog.call("JOBNAME", "42")
For me this works just fine if I use this as a dummy class in order to make the code compile (because you did not provide the source code):
class BuildLogProvider {
def getBuildLog(def jobName, def buildNumber) {}
}

extract parameters from a jenkins previous build

I am working on Jenkins version 2.32.1 pipeline. I want to extract the parameters that were chosen in the previous build of my job.
In our previous Jenkins instance ( 1.627 ) we were using jenkins.model.Jenkins.instance.getItem(job).lastBuild.getBuildVariables().get(param);
For some reason this is not working in this version (I also tried disabling the sandbox).
Any pointers on how to accomplish it?
Simplified version of the previous script:
def build = Jenkins.get().getItems(org.jenkinsci.plugins.workflow.job.WorkflowJob).find {it.displayName == 'YOUR_JOB_NAME_HERE'}?.getLastBuild()
build.actions.find{ it instanceof ParametersAction }?.parameters.each {echo "${it.name}=${it.value}"}
Actually a little bit shorter version for those who want to get the params for the current build from the previous run and is working on new 2+ Jenkins versions.
To get 1 particular parameter:
def cls = currentBuild.getPreviousBuild().getRawBuild().actions.find{ it instanceof ParametersAction }?.parameters.find{it.name == 'cls'}?.value
Get all params respectfully:
def cls = currentBuild.getPreviousBuild().getRawBuild().actions.find{ it instanceof ParametersAction }?.parameters
Something like this might work, based on https://stackoverflow.com/a/19564602/3920342:
def h = hudson.model.Hudson.instance
def r = null
h.getItems(org.jenkinsci.plugins.workflow.job.WorkflowJob).each {project ->
if (project.displayName.equals('YOUR_JOB_NAME')) {
r = project
}
}
r.getBuilds().findAll { b -> // here we loop over all past builds, apply some filter if necessary
def p = b?.actions.find{ it instanceof ParametersAction }?.parameters
p.each {
echo "parameter ${it.name}: ${it.value}"
}
}
For those who are not able to access getActions() due to admin permissions i.e. facing the following error:
Scripts not permitted to use method hudson.model.Actionable getActions
They can copy the parameter variables to the env and get them using build.previousBuild.buildVariables
stage('Prepare environment') {
steps {
script {
env.MY_PARAM_COPY = "${MY_PARAM}"
}
}
}
println("MY_PARAM in previous build: ${currentBuild.previousBuild.buildVariables["MY_PARAM_COPY"]}")
That's how I made it works, answer from #dan.goriaynov and #jherb caused some CPS closure issues for me.
(the gist of the code is to allow only greater TAG number than the previous one to be deployed)
stage('Validate build number') {
def previous_build = currentBuild.getPreviousBuild().getRawBuild();
def PREVIOUS_TAG = '';
for (int i = 0; i < previous_build.allActions.size(); i++) {
if (previous_build.allActions[i] in hudson.model.ParametersAction) {
PREVIOUS_TAG = previous_build.allActions[i].getParameter("TAG").value
}
}
if (PREVIOUS_TAG.toInteger() > TAG.toInteger()) {
echo PREVIOUS_TAG
error('TAG number needs to be greater than the previous one')
}
}

Update JIRA subtask Fix Version when parent issue Fix Version updated

When an issue is created, the Fix Version field is set to a particular value (say 2.0). Then subtasks are created, and they inherit this value. So far so good. But if later, the issue's Fix Version value is modified (to say 1.0), the subtasks still keep the 2.0 fix version value.
Is there a plugin or technique I can use to keep these fields in sync?
Note: This has been requested as a JIRA feature, but Atlassian doesn't seem to want to do it.
https://jira.atlassian.com/browse/JRA-9016
I know its rather an old question. But here is my code I recently deployed. This is an event listener for issueUpdated event deployed on Script Listener from ScriptRunner plugin. A lot of the code is from Jamie Echlin's examples. It still needs to be tweaked for when a Fix Version field on the Parent is made "empty", it's sub tasks also need to be empty.
package com.custom.listeners
import org.apache.log4j.Category
import com.atlassian.jira.ComponentManager
import com.atlassian.jira.event.issue.AbstractIssueEventListener
import com.atlassian.jira.event.issue.IssueEvent
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.project.version.Version
import com.opensymphony.workflow.InvalidInputException
import com.atlassian.jira.config.SubTaskManager
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.event.type.EventDispatchOption
import java.util.ArrayList
import java.util.Collection
class FixVersionPBI_To_SBI_1 extends AbstractIssueEventListener
{
Category log = Category.getInstance(FixVersionPBI_To_SBI_1.class)
#Override
void issueUpdated(IssueEvent event)
{
try
{
Issue fix_Issue = event.getIssue()
if (fix_Issue.issueTypeObject.name == "Parent issue type" )
{
List changeItems = event.getChangeLog().getRelated("ChildChangeItem")
if( fix_Issue.fixVersions?.name && changeItems.any {it.get('field')=='Fix Version'} )
{
Collection<Version> fixVersions = new ArrayList<Version>();
fixVersions = fix_Issue.getFixVersions()
Collection subTasks = fix_Issue.getSubTasks();
SubTaskManager subTaskManager = ComponentManager.getInstance().getSubTaskManager();
if (subTaskManager.subTasksEnabled && !subTasks.empty)
{
IssueManager issueManager = ComponentManager.getInstance().getIssueManager()
Collection _subTasks = fix_Issue.getSubTaskObjects()
_subTasks.each
{
it.setFixVersions(fixVersions)
issueManager.updateIssue(event.getUser(), it, EventDispatchOption.ISSUE_UPDATED, false)
}
}
}
}
}
catch (ex)
{
log.debug "Event: ${event.getEventTypeId()} fired for ${event.issue} and caught by FixVersionPBI_To_SBI_1"
log.debug (ex.getMessage())
}
}
}
For Jira 6.4
Your subtasks fixversion fields will be update automatically.
Install ScriptRunner plugin.
OPEN your-jira.host/secure/admin/ViewCustomFields.jspa
ADD the new custom field with (Advanced>Scripted field)
OPEN /plugins/servlet/scriptrunner/builtin?section=script_fields and find new field there.
ADD this script, test it and save.
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.config.SubTaskManager;
import com.atlassian.jira.issue.IssueManager;
import com.atlassian.jira.event.type.EventDispatchOption;
def result = "";
def subTasks = issue.getSubTaskObjects()
Collection fixVersions = issue.getFixVersions();
if (!fixVersions.empty) {
SubTaskManager subTaskManager = ComponentAccessor.getSubTaskManager();
if(subTaskManager.subTasksEnabled && !subTasks.empty) {
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser();
subTasks.each {
if (it.statusObject.name != "Closed") {
it.setFixVersions(fixVersions);
IssueManager issueManager = ComponentAccessor.getIssueManager()
result += it.getKey() + " ";
issueManager.updateIssue(user, it, EventDispatchOption.ISSUE_UPDATED, false);
}
}
if (count > 0) {
result += " versions updated";
}
}
}
return result;
To do it manually, you could do query such as "parent=TEST-123" and then a bulk edit. To do it automatically you would need to have a custom listener (e.g. Script Runner) to detect the Issue Updated event and only do something if the change was an issue update. Updating the subtask Fix Versions will also require that the subtasks are reindexed or your searches will not work as expected.
I don't know of a plugin to do this.

Resources