Migrating from Feature Score
- Create an Issue Score field with the same name as your Feature Score field
- Create as many Issue Score Key-Value pair custom field, as many parameter you have in the regarding Feature Score configuration
- Configure your Custom Field Formula as it is in the Feature Score
- Feature Score: ((parameter * multiplier) + (parameter * multiplier) + ... + (parameter * multiplier))
- Issue Score: (("Key_Value custom field name" * multiplier number) + ("Key_Value custom field name" * multiplier number) + ... + ("Key_Value custom field name" * multiplier number))
- Configure your Issue Score ranges to represent the same 4 colors and ranking as in the regarding Feature Score configuration, eg. in case of "Higher is the Better":
- Configure your Issue Score Key-Value pair custom field or Number custom field, as the parameters you have in the regarding Feature Score configuration, eg.:
- Feature Score
- Name: Parameter One
- Multiplier: 1
- Options:
- Name: Option One, Value: 1
- Name: Option Two, Value: 2
- Name: Parameter One
- Issue Score:
- Name: Parameter One
- Options:
- Key: Option One, Value: 1
- Key: Option Two, Value: 2
- Options:
- Name: Parameter One
- Feature Score
- In the end, you should have the same results on your Issue Screen.
Parameter Mapping
Feature Score | Issue Score |
---|---|
Name-Value parameter | Key-Value custom field |
Linear parameter | Number custom field |
Data migration
Manual
If only a few Score fields and/or parameters are created and few Issues are involved in your system, you can also perform the migration manually. Simply click on the values on the relevant tickets page.
Automatic with a scripting tool
If a lot of Score fields and/or parameters are created and there are many Issues involved in your system, you might want to perform the migration using a script tool. Here's how you can do this with Adaptavist ScriptRunner.
Feature Score store the selected values in the database. The values can be retrieved with their ID.
Here is an example of the data structure:
{ "calculatedValue" : 40, "calculatedColor" : "aui-lozenge-success", "tooltip" : "tooltip-sample", "scoreParameterPair": [{ "optionType" : "LINEAR", "scoreParameterId": 1, "scoreParameterOptionId": 1 }, { "optionType" : "NAME-VALUES", "scoreParameterId": 3, "scoreParameterOptionId": 5 }, { "optionType" : "LINEAR", "scoreParameterId": 2, "scoreParameterOptionId": 7 }] }
You should explore your Feature Score parameters' and their values' ID's. You can do that on an Issue page with the help of your browser's developer tool.
- Open an Issue Screen with a configured Feature Score field
- Click on the Feature Score's Edit button
- Click on the Parameter's name with right-click
- Inspect the element
Name-Value Pair IDs
- "score_param_5" belongs to the "Parameter One"
- The ID of this parameter is "5"
- "<option value="9">" and other options belongs to the "Parameter One"'s options
- The ID of the parameters' options are the values of the option
Linear IDs
- "score_param_7" belongs to the "Parameter Linear"
- The ID of this parameter is "7"
- "<option value="1">" and other options belongs to the "Parameter Linear"'s options
- The ID of the parameters' options are the values of the option
You can also retrieve your new Issue Score Key-Value pair custom fields' option ID, or you get get it from the URL when you are editing the option, eg.: /plugins/servlet/issue-score/customfield/key-value/admin/edit?customFieldId=10410&keyValueId=25&fieldConfigId=10514
Based on the known IDs now you can create a script that will map the Feature Score parameters to the new custom fields.
Below you can see an example for the two parameter types and their equivalents with Issue Score. We recommend to create a mapping table with the IDs, eg.:
Name-Value to Key-Value
Feature Score | | | Issue Score | ||||||
---|---|---|---|---|---|---|---|---|
Feature Score Parameter | Type | Option | ID | | | Custom Field | Type | Option | ID |
Parameter One | Name-Value | 5 | | | Parameter One | Issue Score Key-Value | |||
Option One | 9 | | | Option One - 1 | 25 |
Linear to Number
Feature Score | | | Issue Score | |||||
---|---|---|---|---|---|---|---|
Feature Score Parameter | Type | Option | ID | | | Custom Field | Type | Value |
Parameter Linear | Linear | 7 | | | Parameter Number | Number custom field | ||
1 | 1 | | | 1 |
Script example for Jira 7.x
import com.atlassian.jira.component.ComponentAccessor; import com.atlassian.jira.issue.CustomFieldManager; import com.atlassian.jira.issue.fields.CustomField; import com.atlassian.jira.issue.Issue; import com.atlassian.jira.issue.MutableIssue; import com.atlassian.jira.security.JiraAuthenticationContext; import com.atlassian.jira.issue.search.SearchProvider; import com.atlassian.jira.jql.parser.JqlQueryParser; import com.atlassian.jira.util.JiraUtils; import com.atlassian.jira.workflow.WorkflowTransitionUtil; import com.atlassian.jira.workflow.WorkflowTransitionUtilImpl; import com.atlassian.jira.user.util.UserManager; import com.atlassian.crowd.embedded.api.User; import com.atlassian.jira.user.ApplicationUsers; import com.atlassian.jira.user.ApplicationUser; import com.atlassian.jira.issue.util.DefaultIssueChangeHolder; import com.atlassian.jira.issue.ModifiedValue; UserManager userManager = ComponentAccessor.getUserManager(); ApplicationUser adminUserApp = userManager.getUserByName("jira.admin"); // <- Add here the user who has the necessary permissions def AuthContext=ComponentAccessor.getJiraAuthenticationContext(); AuthContext.setLoggedInUser(adminUserApp); def findIssues(String jqlQuery) { def issueManager = ComponentAccessor.issueManager def user = ComponentAccessor.jiraAuthenticationContext.getLoggedInUser() def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser.class) def searchProvider = ComponentAccessor.getComponent(SearchProvider.class) org.apache.lucene.search.Collector collector def query = jqlQueryParser.parseQuery(jqlQuery) def results = searchProvider.search(query, user, com.atlassian.jira.web.bean.PagerFilter.getUnlimitedFilter()) results.issues.collect { issue -> issueManager.getIssueObject(issue.id) } } def currentUser = ComponentAccessor.jiraAuthenticationContext.getLoggedInUser().getName(); def jqlQuery = "project = IMAS" // <- Define your project or Issues with JQL def issues = findIssues(jqlQuery) issues.each { def issueManager = ComponentAccessor.issueManager; def issue = (Issue) it; def customFieldManager = ComponentAccessor.getCustomFieldManager(); def featureScore = customFieldManager.getCustomFieldObjectByName('Feature Score'); // <- Define your FS custom field def featureScoreCFvalue = issue.getCustomFieldValue(featureScore); def keyValueOne = customFieldManager.getCustomFieldObjectByName('Parameter One'); // <- Define your Issue Score Key-Value custom field def keyValueOneCFvalue = issue.getCustomFieldValue(keyValueOne); def numberField = customFieldManager.getCustomFieldObjectByName('Issue Score Number'); // <- Define your Number custom field def numberFieldvalue = issue.getCustomFieldValue(numberField); // Example of the value mapping // IGNORE THE RED STATIC TYPE CHECKING ERROR if(featureScoreCFvalue != null) { def list = featureScoreCFvalue.getScoreParameterPair(); // <- Iterate in your FS data structure for (item in list) { if(item.scoreParameterId == 5){ // <- If "Parameter One" with ID=5 if(item.scoreParameterOptionId == 9){ // <- If "Parameter One"'s selected otpion is "Option One" with ID=9 def changeHolder = new DefaultIssueChangeHolder() keyValueOne.updateValue(null, issue, new ModifiedValue(keyValueOneCFvalue, (Double) 25),changeHolder) // <- Set Issue Score KV pair to "Option One - 1" with ID=25 } } if(item.scoreParameterId == 7){ // <- If "Parameter Linear" with ID=7 if(item.scoreParameterOptionId == 1){ // <- If "Parameter Linear"'s selected otpion is "1" with ID=1 def changeHolder = new DefaultIssueChangeHolder() numberField.updateValue(null, issue, new ModifiedValue(numberFieldvalue, (Double) 1),changeHolder) // <- Set number field to "1" } } } } }
Script example for Jira 8.x
import com.atlassian.jira.component.ComponentAccessor; import com.atlassian.jira.issue.CustomFieldManager; import com.atlassian.jira.issue.fields.CustomField; import com.atlassian.jira.issue.Issue; import com.atlassian.jira.issue.MutableIssue; import com.atlassian.jira.security.JiraAuthenticationContext; import com.atlassian.jira.bc.issue.search.SearchService; import com.atlassian.jira.jql.parser.JqlQueryParser; import com.atlassian.jira.util.JiraUtils; import com.atlassian.jira.workflow.WorkflowTransitionUtil; import com.atlassian.jira.workflow.WorkflowTransitionUtilImpl; import com.atlassian.jira.user.util.UserManager; import com.atlassian.crowd.embedded.api.User; import com.atlassian.jira.user.ApplicationUsers; import com.atlassian.jira.user.ApplicationUser; import com.atlassian.jira.issue.util.DefaultIssueChangeHolder; import com.atlassian.jira.issue.ModifiedValue; UserManager userManager = ComponentAccessor.getUserManager(); ApplicationUser adminUserApp = userManager.getUserByName("jira.admin"); // <- Add here the user who has the necessary permissions def AuthContext=ComponentAccessor.getJiraAuthenticationContext(); AuthContext.setLoggedInUser(adminUserApp); def findIssues(String jqlQuery) { def issueManager = ComponentAccessor.issueManager def user = ComponentAccessor.jiraAuthenticationContext.getLoggedInUser() def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser.class) def searchService = ComponentAccessor.getComponent(SearchService.class) org.apache.lucene.search.Collector collector def query = jqlQueryParser.parseQuery(jqlQuery) def results = searchService.search(user, query, com.atlassian.jira.web.bean.PagerFilter.getUnlimitedFilter()) results.results.collect { issue -> issueManager.getIssueObject(issue.id) } } def currentUser = ComponentAccessor.jiraAuthenticationContext.getLoggedInUser().getName(); def jqlQuery = "project = IMAS" // <- Define your project or Issues with JQL def issues = findIssues(jqlQuery) issues.each { def issueManager = ComponentAccessor.issueManager; def issue = (Issue) it; def customFieldManager = ComponentAccessor.getCustomFieldManager(); def featureScore = customFieldManager.getCustomFieldObjectByName('Feature Score'); // <- Define your FS custom field def featureScoreCFvalue = issue.getCustomFieldValue(featureScore); def keyValueOne = customFieldManager.getCustomFieldObjectByName('Parameter One'); // <- Define your Issue Score Key-Value custom field def keyValueOneCFvalue = issue.getCustomFieldValue(keyValueOne); def numberField = customFieldManager.getCustomFieldObjectByName('Issue Score Number'); // <- Define your Number custom field def numberFieldvalue = issue.getCustomFieldValue(numberField); // Example of the value mapping // IGNORE THE RED STATIC TYPE CHECKING ERROR if(featureScoreCFvalue != null) { def list = featureScoreCFvalue.getScoreParameterPair(); // <- Iterate in your FS data structure for (item in list) { if(item.scoreParameterId == 5){ // <- If "Parameter One" with ID=5 if(item.scoreParameterOptionId == 9){ // <- If "Parameter One"'s selected otpion is "Option One" with ID=9 def changeHolder = new DefaultIssueChangeHolder() keyValueOne.updateValue(null, issue, new ModifiedValue(keyValueOneCFvalue, (Double) 25),changeHolder) // <- Set Issue Score KV pair to "Option One - 1" with ID=25 } } if(item.scoreParameterId == 7){ // <- If "Parameter Linear" with ID=7 if(item.scoreParameterOptionId == 1){ // <- If "Parameter Linear"'s selected otpion is "1" with ID=1 def changeHolder = new DefaultIssueChangeHolder() numberField.updateValue(null, issue, new ModifiedValue(numberFieldvalue, (Double) 1),changeHolder) // <- Set number field to "1" } } } } }
After running the script, run the Bulk Update on these issues to apply the changes and calculate the Issue core as described here.