For your requirement, you could try something like this:-
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def customFieldManager = ComponentAccessor.customFieldManager
def issueManager = ComponentAccessor.issueManager
def issueLinkManager = ComponentAccessor.issueLinkManager
def issueFactory = ComponentAccessor.issueFactory
def subtaskManager = ComponentAccessor.subTaskManager
def originalIssuesInEpic = [] as ArrayList<Issue>
def changeHolder = new DefaultIssueChangeHolder()
/**
* Create a clone of an issue
*/
static def clonedIssue(Issue issue) {
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def issueManager = ComponentAccessor.issueManager
def issueFactory = ComponentAccessor.issueFactory
def cloneIssueFactory = issueFactory.cloneIssue(issue)
def newIssueParams = ["issue": cloneIssueFactory] as Map<String, Object>
issueManager.createIssueObject(loggedInUser, newIssueParams)
}
if (issue.issueType.name=="Epic") {
/**
* Cloning of the Epic
*/
def epicClone = clonedIssue(issue)
def epic = issueManager.getIssueByCurrentKey(epicClone.key)
def epicName = customFieldManager.getCustomFieldObjectsByName("Epic Name")[0]
def originalEpicName = issue.getCustomFieldValue(epicName)
epicName.updateValue(null, epic, new ModifiedValue(epic.getCustomFieldValue(epicName), "${originalEpicName} Clone".toString()),changeHolder)
issueManager.updateIssue(loggedInUser, epic, EventDispatchOption.ISSUE_UPDATED, false)
/**
* Extraction of Original Issues from Original Epic
*/
def links = issueLinkManager.getOutwardLinks(issue.id)
links.findAll {
def name = it.issueLinkType.name
def destinationObject = it.destinationObject
if (name == "Epic-Story Link") {
originalIssuesInEpic.add(destinationObject)
}
}
/**
* Cloning of Original Issue from the Epic and Adding it
* to the Cloned Epic
*/
originalIssuesInEpic.findAll {
def clonedIssue = clonedIssue(it)
def subtasks = it.subTaskObjects
if (subtasks) {
subtasks.each {subtask ->
def toClone = issueFactory.cloneIssueWithAllFields(subtask)
def cloned = issueManager.createIssueObject(clonedIssue.reporter, toClone)
subtaskManager.createSubTaskIssueLink(clonedIssue, cloned, clonedIssue.reporter)
}
}
def targetField = customFieldManager.getCustomFieldObjects(clonedIssue).findByName("Epic Link")
targetField.updateValue(null, clonedIssue, new ModifiedValue(clonedIssue.getCustomFieldValue(targetField), epic), changeHolder)
}
}
Please note the working sample code above is not 100% exact to your environment. Hence, you will need to make the required modifications.
This sample code above is for the Post-Function, i.e. when you transition the issue to a specific status, the cloning of the Epic, its issues and sub-tasks will take place.
I hope this helps to solve your question. :)
Thank you and Kind Regards,
Ram
Hi Ram, tks for the script. it works fine.
however, the script have some errors when writing into the script window. any idea?
tks
Antonio
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Glad to hear the script worked.
To fix the error messages, you need to make a minor modification in the code, i.e. modify this section
static def clonedIssue(Issue issue) {
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def issueManager = ComponentAccessor.issueManager
def issueFactory = ComponentAccessor.issueFactory
def cloneIssueFactory = issueFactory.cloneIssue(issue)
def newIssueParams = ["issue": cloneIssueFactory] as Map<String, Object>
issueManager.createIssueObject(loggedInUser, newIssueParams)
}
to
static Issue clonedIssue(Issue issue) {
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def issueManager = ComponentAccessor.issueManager
def issueFactory = ComponentAccessor.issueFactory
def cloneIssueFactory = issueFactory.cloneIssue(issue)
def newIssueParams = ["issue": cloneIssueFactory] as Map<String, Object>
issueManager.createIssueObject(loggedInUser, newIssueParams)
}
so the updated code will be like:-
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def customFieldManager = ComponentAccessor.customFieldManager
def issueManager = ComponentAccessor.issueManager
def issueLinkManager = ComponentAccessor.issueLinkManager
def issueFactory = ComponentAccessor.issueFactory
def subtaskManager = ComponentAccessor.subTaskManager
def originalIssuesInEpic = [] as ArrayList<Issue>
def changeHolder = new DefaultIssueChangeHolder()
/**
* Create a clone of an issue
*/
static Issue clonedIssue(Issue issue) {
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def issueManager = ComponentAccessor.issueManager
def issueFactory = ComponentAccessor.issueFactory
def cloneIssueFactory = issueFactory.cloneIssue(issue)
def newIssueParams = ["issue": cloneIssueFactory] as Map<String, Object>
issueManager.createIssueObject(loggedInUser, newIssueParams)
}
if (issue.issueType.name=="Epic") {
/**
* Cloning of the Epic
*/
def epicClone = clonedIssue(issue)
def epic = issueManager.getIssueByCurrentKey(epicClone.key)
def epicName = customFieldManager.getCustomFieldObjectsByName("Epic Name")[0]
def originalEpicName = issue.getCustomFieldValue(epicName)
epicName.updateValue(null, epic, new ModifiedValue(epic.getCustomFieldValue(epicName), "${originalEpicName} Clone".toString()),changeHolder)
issueManager.updateIssue(loggedInUser, epic, EventDispatchOption.ISSUE_UPDATED, false)
/**
* Extraction of Original Issues from Original Epic
*/
def links = issueLinkManager.getOutwardLinks(issue.id)
links.findAll {
def name = it.issueLinkType.name
def destinationObject = it.destinationObject
if (name == "Epic-Story Link") {
originalIssuesInEpic.add(destinationObject)
}
}
/**
* Cloning of Original Issue from the Epic and Adding it
* to the Cloned Epic
*/
originalIssuesInEpic.findAll {
def clonedIssue = clonedIssue(it)
def subtasks = it.subTaskObjects
if (subtasks) {
subtasks.each {subtask ->
def toClone = issueFactory.cloneIssueWithAllFields(subtask)
def cloned = issueManager.createIssueObject(clonedIssue.reporter, toClone)
subtaskManager.createSubTaskIssueLink(clonedIssue, cloned, clonedIssue.reporter)
}
}
def targetField = customFieldManager.getCustomFieldObjects(clonedIssue).findByName("Epic Link")
targetField.updateValue(null, clonedIssue, new ModifiedValue(clonedIssue.getCustomFieldValue(targetField), epic), changeHolder)
}
}
I hope this helps to solve your question. :)
Thank you and Kind regards,
Ram
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @Ram Kumar Aravindakshan _Adaptavist_ can you kindly help on another thing.
i notice, the create date is being cloned from the original Epic.
I need the create date to reflect the current date (and not the date that is in the epic source).
can you advise?
tks
Antonio
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Apologies for the Delay.
So, if you want to include the updated timestamp when the Epic and its issues are cloned, you will need to modify the code to something like this:-
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.*
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.user.ApplicationUser
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def customFieldManager = ComponentAccessor.customFieldManager
def issueManager = ComponentAccessor.issueManager
def issueLinkManager = ComponentAccessor.issueLinkManager
def issueFactory = ComponentAccessor.issueFactory
def subtaskManager = ComponentAccessor.subTaskManager
def changeHolder = new DefaultIssueChangeHolder()
//Create a clone of an issue
static Issue clonedIssue(Issue issue, ApplicationUser loggedInUser, IssueFactory issueFactory, IssueManager issueManager) {
def cloneIssueFactory = issueFactory.cloneIssue(issue)
cloneIssueFactory.setCreated( new Date(System.currentTimeMillis()).toTimestamp() )
def newIssueParams = ['issue': cloneIssueFactory] as Map<String, Object>
issueManager.createIssueObject(loggedInUser, newIssueParams)
}
if (issue.issueType.name == 'Epic') {
//Cloning of the Epic
def epicClone = clonedIssue(issue, loggedInUser, issueFactory, issueManager)
def epic = issueManager.getIssueByCurrentKey(epicClone.key)
def epicName = customFieldManager.getCustomFieldObjectsByName('Epic Name').first()
def originalEpicName = issue.getCustomFieldValue(epicName)
epicName.updateValue(null, epic, new ModifiedValue(epic.getCustomFieldValue(epicName), "${originalEpicName} Clone".toString()), changeHolder)
issueManager.updateIssue(loggedInUser, epic, EventDispatchOption.ISSUE_UPDATED, false)
//Extraction of Original Issue(s) from Original Epic
def links = issueLinkManager.getOutwardLinks(issue.id)
def originalIssuesInEpic= links.findAll { it.issueLinkType.name == 'Epic-Story Link' }*.destinationObject
//Cloning of Original Issue(s) from the Original Epic and Adding it to the Cloned Epic
originalIssuesInEpic.each {
def clonedIssue = clonedIssue(it, loggedInUser, issueFactory, issueManager)
def subtasks = it.subTaskObjects
if (subtasks) {
subtasks.each {subtask ->
def mutableSubtask = subtasks as MutableIssue
mutableSubtask.setCreated(new Date(System.currentTimeMillis()).toTimestamp())
def toClone = issueFactory.cloneIssueWithAllFields(mutableSubtask)
def cloned = issueManager.createIssueObject(clonedIssue.reporter, toClone)
subtaskManager.createSubTaskIssueLink(clonedIssue, cloned, clonedIssue.reporter)
}
}
def targetField = customFieldManager.getCustomFieldObjects(clonedIssue).findByName('Epic Link')
targetField.updateValue(null, clonedIssue, new ModifiedValue(clonedIssue.getCustomFieldValue(targetField), epic), changeHolder)
}
}
The main modification I have made is in this section:-
//Create a clone of an issue
static Issue clonedIssue(Issue issue, ApplicationUser loggedInUser, IssueFactory issueFactory, IssueManager issueManager) {
def cloneIssueFactory = issueFactory.cloneIssue(issue)
cloneIssueFactory.setCreated( new Date(System.currentTimeMillis()).toTimestamp() )
def newIssueParams = ['issue': cloneIssueFactory] as Map<String, Object>
issueManager.createIssueObject(loggedInUser, newIssueParams)
}
And also in this section:-
subtasks.each {subtask ->
def mutableSubtask = subtasks as MutableIssue
mutableSubtask.setCreated(new Date(System.currentTimeMillis()).toTimestamp())
def toClone = issueFactory.cloneIssueWithAllFields(mutableSubtask)
def cloned = issueManager.createIssueObject(clonedIssue.reporter, toClone)
subtaskManager.createSubTaskIssueLink(clonedIssue, cloned, clonedIssue.reporter)
}
I have mainly updated the issue using the setCreated method to update the Creation timestamp of the Clone Epic, its Issues and Sub-Tasks.
I hope this helps to solve your question. :)
Thank you and Kind regards,
Ram
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @Ram Kumar Aravindakshan _Adaptavist_
tks for the code, however, i have the following error:
tks
2022-03-22 17:41:22,920 ERROR [workflow.AbstractScriptWorkflowFunction]: Workflow script has failed on issue DSM-1 for user 'ptzurace'. View here: https://deaww02349.emea.zurich.dev:8443/secure/admin/workflows/ViewWorkflowTransition.jspa?workflowMode=live&workflowName=DSM%3A+Project+Management+Workflow&descriptorTab=postfunctions&workflowTransition=71&highlight=2
java.lang.NoClassDefFoundError: groovy/lang/GroovyObject
at Script14$_run_closure2$_closure3.doCall(Script14.groovy:44)
at Script14$_run_closure2.doCall(Script14.groovy:43)
at Script14.run(Script14.groovy:39)
at com.onresolve.scriptrunner.runner.AbstractScriptRunner.runScriptAndGetContext(AbstractScriptRunner.groovy:180)
at com.onresolve.scriptrunner.runner.AbstractScriptRunner$runScriptAndGetContext$2.callCurrent(Unknown Source)
at com.onresolve.scriptrunner.runner.AbstractScriptRunner.runScriptAndGetContext(AbstractScriptRunner.groovy:299)
at com.onresolve.scriptrunner.runner.AbstractScriptRunner$runScriptAndGetContext$1.callCurrent(Unknown Source)
at com.onresolve.scriptrunner.runner.AbstractScriptRunner.runScript(AbstractScriptRunner.groovy:311)
at com.onresolve.scriptrunner.runner.ScriptRunner$runScript$10.call(Unknown Source)
at com.onresolve.scriptrunner.canned.jira.utils.TypedCustomScriptDelegate.execute(TypedCustomScriptDelegate.groovy:19)
at com.onresolve.scriptrunner.canned.jira.utils.TypedCustomScriptDelegate$execute$0.call(Unknown Source)
at com.onresolve.scriptrunner.canned.jira.workflow.postfunctions.CustomScriptFunction.execute(CustomScriptFunction.groovy:53)
at com.onresolve.scriptrunner.canned.jira.workflow.postfunctions.CustomScriptFunction$execute.callCurrent(Unknown Source)
at com.onresolve.scriptrunner.canned.jira.workflow.AbstractWorkflowCannedScript.execute(AbstractWorkflowCannedScript.groovy:23)
at com.onresolve.scriptrunner.canned.jira.workflow.AbstractWorkflowCannedScript$execute$1.call(Unknown Source)
at com.onresolve.scriptrunner.jira.workflow.AbstractScriptWorkflowFunction$_run_closure2.doCall(AbstractScriptWorkflowFunction.groovy:89)
at com.onresolve.scriptrunner.jira.workflow.AbstractScriptWorkflowFunction$_run_closure2.doCall(AbstractScriptWorkflowFunction.groovy)
at com.onresolve.scriptrunner.runner.diag.DiagnosticsManagerImpl$DiagnosticsExecutionHandlerImpl$_execute_closure1.doCall(DiagnosticsManagerImpl.groovy:370)
at com.onresolve.scriptrunner.runner.diag.DiagnosticsManagerImpl$DiagnosticsExecutionHandlerImpl$_execute_closure1.doCall(DiagnosticsManagerImpl.groovy)
at com.onresolve.scriptrunner.runner.ScriptExecutionRecorder.withRecording(ScriptExecutionRecorder.groovy:13)
at com.onresolve.scriptrunner.runner.ScriptExecutionRecorder$withRecording.call(Unknown Source)
at com.onresolve.scriptrunner.runner.diag.DiagnosticsManagerImpl$DiagnosticsExecutionHandlerImpl.execute(DiagnosticsManagerImpl.groovy:368)
at com.onresolve.scriptrunner.runner.diag.DiagnosticsExecutionHandler$execute$3.call(Unknown Source)
at com.onresolve.scriptrunner.jira.workflow.AbstractScriptWorkflowFunction.run(AbstractScriptWorkflowFunction.groovy:82)
Caused by: java.lang.ClassNotFoundException: groovy.lang.GroovyObject
... 24 more
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
I didn't have any error messages when I tested it in my environments.
Could you please share the updated code you were using so I can review it?
Thank you and Kind regards,
Ram
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @Ram Kumar Aravindakshan _Adaptavist_
i used this one tks
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.*
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.user.ApplicationUser
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def customFieldManager = ComponentAccessor.customFieldManager
def issueManager = ComponentAccessor.issueManager
def issueLinkManager = ComponentAccessor.issueLinkManager
def issueFactory = ComponentAccessor.issueFactory
def subtaskManager = ComponentAccessor.subTaskManager
def changeHolder = new DefaultIssueChangeHolder()
//Create a clone of an issue
static Issue clonedIssue(Issue issue, ApplicationUser loggedInUser, IssueFactory issueFactory, IssueManager issueManager) {
def cloneIssueFactory = issueFactory.cloneIssue(issue)
cloneIssueFactory.setCreated( new Date(System.currentTimeMillis()).toTimestamp() )
def newIssueParams = ['issue': cloneIssueFactory] as Map<String, Object>
issueManager.createIssueObject(loggedInUser, newIssueParams)
}
if (issue.issueType.name == 'Epic') {
//Cloning of the Epic
def epicClone = clonedIssue(issue, loggedInUser, issueFactory, issueManager)
def epic = issueManager.getIssueByCurrentKey(epicClone.key)
def epicName = customFieldManager.getCustomFieldObjectsByName('Epic Name').first()
def originalEpicName = issue.getCustomFieldValue(epicName)
epicName.updateValue(null, epic, new ModifiedValue(epic.getCustomFieldValue(epicName), "${originalEpicName} Clone".toString()), changeHolder)
issueManager.updateIssue(loggedInUser, epic, EventDispatchOption.ISSUE_UPDATED, false)
//Extraction of Original Issue(s) from Original Epic
def links = issueLinkManager.getOutwardLinks(issue.id)
def originalIssuesInEpic= links.findAll { it.issueLinkType.name == 'Epic-Story Link' }*.destinationObject
//Cloning of Original Issue(s) from the Original Epic and Adding it to the Cloned Epic
originalIssuesInEpic.each {
def clonedIssue = clonedIssue(it, loggedInUser, issueFactory, issueManager)
def subtasks = it.subTaskObjects
if (subtasks) {
subtasks.each {subtask ->
def mutableSubtask = subtasks as MutableIssue
mutableSubtask.setCreated(new Date(System.currentTimeMillis()).toTimestamp())
def toClone = issueFactory.cloneIssueWithAllFields(mutableSubtask)
def cloned = issueManager.createIssueObject(clonedIssue.reporter, toClone)
subtaskManager.createSubTaskIssueLink(clonedIssue, cloned, clonedIssue.reporter)
}
}
def targetField = customFieldManager.getCustomFieldObjects(clonedIssue).findByName('Epic Link')
targetField.updateValue(null, clonedIssue, new ModifiedValue(clonedIssue.getCustomFieldValue(targetField), epic), changeHolder)
}
}
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
The error message doesn't seem to match the code you are using.
Aside from this post-function, do you have any other post-function configured for this project? If so, could you temporarily disable them and try to retest it once again and see if the error message is still returned?
Thank you and Kind Regards,
Ram
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @[deleted] , I have tried your script and edited two things:
1. Line 27 - changed the key to an epic that should be cloned:
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi @Anna Hummel
You can take a look at the updated code in the Adaptavist Library.
Hope this helps to answer your question.
Thank you and Kind regards,
Ram
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hello! Is it also possible to create for each subtask that was cloned in that project a new Task in another project?
I tried something like the method below:
but it seems I am getting a bunch of errors when trying to create a new issue in the same code block when the sub-tasks are cloned.
Any insight would be very helpful!
Thank you!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hello, I copied the last script and I had the same error,
If anyone is intersted:
What the error was was in the subtasks.each module.
The line:
mutableSubtask.setCreated(new Date(System.currentTimeMillis()).toTimestamp())
must be after
def toClone = issueFactory.cloneIssueWithAllFields(mutableSubtask)
So the final code is:
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.*
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.user.ApplicationUser
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def customFieldManager = ComponentAccessor.customFieldManager
def issueManager = ComponentAccessor.issueManager
def issueLinkManager = ComponentAccessor.issueLinkManager
def issueFactory = ComponentAccessor.issueFactory
def subtaskManager = ComponentAccessor.subTaskManager
def issue = issueManager.getIssueByCurrentKey("SAFEDEMO-35")
def changeHolder = new DefaultIssueChangeHolder()
//Create a clone of an issue
static Issue clonedIssue(Issue issue, ApplicationUser loggedInUser, IssueFactory issueFactory, IssueManager issueManager) {
def cloneIssueFactory = issueFactory.cloneIssue(issue)
cloneIssueFactory.setCreated( new Date(System.currentTimeMillis()).toTimestamp() )
def newIssueParams = ['issue': cloneIssueFactory] as Map<String, Object>
issueManager.createIssueObject(loggedInUser, newIssueParams)
}
def epic
if (issue.issueType.name == 'Epic') {
//Cloning of the Epic
def epicClone = clonedIssue(issue, loggedInUser, issueFactory, issueManager)
epic = issueManager.getIssueByCurrentKey(epicClone.key)
def epicName = customFieldManager.getCustomFieldObjectsByName('Epic Name').first()
def originalEpicName = issue.getCustomFieldValue(epicName)
epicName.updateValue(null, epic, new ModifiedValue(epic.getCustomFieldValue(epicName), "${originalEpicName} Clone".toString()), changeHolder)
issueManager.updateIssue(loggedInUser, epic, EventDispatchOption.ISSUE_UPDATED, false)
//Extraction of Original Issue(s) from Original Epic
def links = issueLinkManager.getOutwardLinks(issue.id)
def originalIssuesInEpic= links.findAll { it.issueLinkType.name == 'Epic-Story Link' }*.destinationObject
//Cloning of Original Issue(s) from the Original Epic and Adding it to the Cloned Epic
originalIssuesInEpic.each {
def clonedIssue = clonedIssue(it, loggedInUser, issueFactory, issueManager)
def subtasks = it.subTaskObjects
if (subtasks) {
subtasks.each {subtask ->
def mutableSubtask = subtask as MutableIssue
def toClone = issueFactory.cloneIssue(mutableSubtask)
mutableSubtask.setCreated(new Date(System.currentTimeMillis()).toTimestamp())
def cloned = issueManager.createIssueObject(clonedIssue.reporter, toClone)
subtaskManager.createSubTaskIssueLink(clonedIssue, cloned, clonedIssue.reporter)
log.warn(subtask.key)
}
}
def targetField = customFieldManager.getCustomFieldObjects(clonedIssue).findByName('Epic Link')
targetField.updateValue(null, clonedIssue, new ModifiedValue(clonedIssue.getCustomFieldValue(targetField), epic), changeHolder)
}
}
return epic
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
how would I include all the customfields of the Epic and the story points of the Story that gets cloned with the incoming Epic?
Disregard, I figured it out. The line of code that states def cloneIssueFactory = issueFactory.cloneIssue(issue)
Change that to
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Online forums and learning are now in one easy-to-use experience.
By continuing, you accept the updated Community Terms of Use and acknowledge the Privacy Policy. Your public name, photo, and achievements may be publicly visible and available in search engines.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.