How to migrate Original Story Points in Jira Data Center
Platform Notice: Data Center Only - This article only applies to Atlassian products on the Data Center platform.
Note that this KB was created for the Data Center version of the product. Data Center KBs for non-Data-Center-specific features may also work for Server versions of the product, however they have not been tested. Support for Server* products ended on February 15th 2024. If you are running a Server product, you can visit the Atlassian Server end of support announcement to review your migration options.
*Except Fisheye and Crucible
Summary
Jira Cloud does not contain the Original Story Points fields. If you are planning a migration to Jira Cloud these values will be lost on migration. This article will outline how to do this.
Background
Original story points were created as part of Advanced Roadmaps and have long since been deprecated.
See the following Atlassian Community post for further details.
While the Community post indicates that Original Story Points are not visible directly on Jira issues, these values have appeared on Jira issues since then.
Solution
To be able to migrate this information to Jira Cloud, you can create a custom field and copy the existing data to the new field.
Create a custom field
Navigate to Administration ⚙ > Issues > Custom Fields.
Create a Number field as outlined in our documentation.
Copy the field data
You have two options to copy the data.
Option 1: Create an automation rule
Navigate to Administration ⚙ > System > Automation rules.
Create a rule with the trigger component Scheduled.
Use the JQL
"Original story points" is not EMPTY
for the section When rule executes...Add the Edit Issue action
Use smart values in the Additional fields section under More Options.
Example
{"fields": {"StoryOrigin": "{{triggerIssue.Original story points}}"}}
Replace 'StoryOrigin' with the name of your custom field
Save and manually run the rule against your projects.
Option 2: Use a Python script to copy
Use the following Python script that will copy values from the Original Story Points field to the new numeric custom field
import requests
import json
# Jira configuration
JIRA_BASE_URL = "https://your-jira-instance.com"
API_TOKEN = "your-pat-token" # Personal Access Token
# Custom field names and IDs
ORIGINAL_POINTS_NAME = "Original story points" # Custom field name as shown in Jira
ORIGINAL_POINTS_FIELD = "customfield_11203" # Custom field ID for updates
PLANNING_ESTIMATE_FIELD = "customfield_10002" # Planning Estimate custom field ID
# Headers with Bearer token authentication
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": f"Bearer {API_TOKEN}"
}
def get_issues_with_original_points():
"""
Search for all issues that have a value in Original story points field
"""
# Use the custom field name in the JQL query
jql = f'"{ORIGINAL_POINTS_NAME}" is not EMPTY'
start_at = 0
max_results = 100
all_issues = []
while True:
search_url = f"{JIRA_BASE_URL}/rest/api/2/search"
payload = {
"jql": jql,
"startAt": start_at,
"maxResults": max_results,
"fields": [ORIGINAL_POINTS_FIELD, PLANNING_ESTIMATE_FIELD]
}
try:
response = requests.post(
search_url,
headers=headers,
json=payload
)
response.raise_for_status() # Raise an exception for bad status codes
except requests.exceptions.RequestException as e:
print(f"Error searching issues: {e}")
print(f"Response status code: {response.status_code}")
print(f"Response text: {response.text}")
return []
data = response.json()
issues = data.get('issues', [])
all_issues.extend(issues)
if len(issues) < max_results:
break
start_at += max_results
return all_issues
def update_planning_estimate(issue_key, points):
"""
Update the Planning Estimate field for a given issue
"""
update_url = f"{JIRA_BASE_URL}/rest/api/2/issue/{issue_key}"
payload = {
"fields": {
PLANNING_ESTIMATE_FIELD: points
}
}
try:
response = requests.put(
update_url,
headers=headers,
json=payload
)
response.raise_for_status()
return True
except requests.exceptions.RequestException as e:
print(f"Error updating issue {issue_key}: {e}")
print(f"Response status code: {response.status_code}")
print(f"Response text: {response.text}")
return False
def main():
# Get all issues with Original story points
print("Fetching issues with Original story points...")
issues = get_issues_with_original_points()
print(f"Found {len(issues)} issues to process")
# Process each issue
success_count = 0
error_count = 0
for issue in issues:
issue_key = issue['key']
original_points = issue['fields'].get(ORIGINAL_POINTS_FIELD)
current_planning_estimate = issue['fields'].get(PLANNING_ESTIMATE_FIELD)
# Skip if Original points is None or if Planning Estimate already has the same value
if original_points is None or original_points == current_planning_estimate:
continue
print(f"Processing {issue_key}: Copying {original_points} to Planning Estimate")
if update_planning_estimate(issue_key, original_points):
success_count += 1
else:
error_count += 1
print("\nSummary:")
print(f"Successfully updated: {success_count}")
print(f"Errors: {error_count}")
if __name__ == "__main__":
main()
Remove the Original Story Points from any screen
You can easily prevent users from adding or modifying the field further by removing it from all available screens via Administration ⚙ > Issues
Under Fields (the left-side panel), select Field configurations.
Select the field configuration.
Find the Original Story Points in the list and select Screens.
Change the associations and select Update.
Always back up your data before making any database modifications. If possible, test any alter, insert, update, or delete SQL commands on a staging server first.
Optional: Unlock the Original Story Points field to include a message not to use it
# replace customfield_11203 with the ID of the Original Story Points custom field ID UPDATE managedconfigurationitem set managed='false' where item_id = 'customfield_11203';
Modify the description of the Original Story Points with something like:
*DO NOT USE* - Replaced with (new numeric) custom field
Lock the Original Story Points field again
# replace customfield_11203 with the ID of the Original Story Points custom field ID UPDATE managedconfigurationitem set managed='true' where item_id = 'customfield_11203';
Was this helpful?